org.sventon.cache.direntrycache.DirEntryCacheUpdater.java Source code

Java tutorial

Introduction

Here is the source code for org.sventon.cache.direntrycache.DirEntryCacheUpdater.java

Source

/*
 * ====================================================================
 * Copyright (c) 2005-2012 sventon project. All rights reserved.
 *
 * This software is licensed as described in the file LICENSE, which
 * you should have received as part of this distribution. The terms
 * are also available at http://www.sventon.org.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */
package org.sventon.cache.direntrycache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StopWatch;
import org.sventon.DirEntryNotFoundException;
import org.sventon.SVNConnection;
import org.sventon.SVNConnectionFactory;
import org.sventon.SventonException;
import org.sventon.appl.Application;
import org.sventon.appl.RepositoryConfiguration;
import org.sventon.cache.CacheException;
import org.sventon.cache.DirEntryCacheManager;
import org.sventon.model.ChangedPath;
import org.sventon.model.DirEntry;
import org.sventon.model.LogEntry;
import org.sventon.model.RepositoryName;
import org.sventon.repository.RepositoryChangeListener;
import org.sventon.repository.RevisionUpdate;
import org.sventon.service.RepositoryService;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Class responsible for updating one or more entry cache instances.
 *
 * @author jesper@sventon.org
 */
public final class DirEntryCacheUpdater implements RepositoryChangeListener {

    /**
     * The static logging instance.
     */
    private static final Log LOGGER = LogFactory.getLog(DirEntryCacheUpdater.class);

    /**
     * The DirEntryCacheManager instance.
     */
    private final DirEntryCacheManager cacheManager;

    /**
     * The application.
     */
    private final Application application;

    /**
     * Service.
     */
    private RepositoryService repositoryService;

    /**
     * The repository factory.
     */
    private SVNConnectionFactory connectionFactory;

    /**
     * Constructor.
     *
     * @param cacheManager The DirEntryCacheManager instance.
     * @param application  Application
     */
    public DirEntryCacheUpdater(final DirEntryCacheManager cacheManager, final Application application) {
        LOGGER.info("Starting");
        this.cacheManager = cacheManager;
        this.application = application;
    }

    /**
     * Updates the cache with the given revisions.
     *
     * @param revisionUpdate The updated revisions.
     */
    public void update(final RevisionUpdate revisionUpdate) {
        final RepositoryName repositoryName = revisionUpdate.getRepositoryName();

        LOGGER.info("Listener got [" + revisionUpdate.getRevisions().size()
                + "] updated revision(s) for repository: " + repositoryName);

        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        SVNConnection connection = null;

        try {
            final DirEntryCache entryCache = cacheManager.getCache(repositoryName);
            final RepositoryConfiguration configuration = application.getConfiguration(repositoryName);
            connection = connectionFactory.createConnection(repositoryName, configuration.getSVNURL(),
                    configuration.getCacheCredentials());
            updateInternal(entryCache, connection, revisionUpdate);
        } catch (final Exception ex) {
            LOGGER.warn("Could not update cache instance [" + repositoryName + "]", ex);
        } finally {
            if (connection != null) {
                connection.closeSession();
            }
        }

        stopWatch.stop();
        LOGGER.info("Update completed in [" + stopWatch.getTotalTimeSeconds() + "] seconds");
    }

    /**
     * Internal update method. Made protected for testing reasons only.
     * <p/>
     * <table>
     * <tr><th>Type</th><th>Description</th><th>Action</th></tr>
     * <tr><td>'A'</td><td>Added</td><td>Entry is added</td></tr>
     * <tr><td>'D'</td><td>Deleted</td><td>Entry is removed</td></tr>
     * <tr><td>'M'</td><td>Modified</td><td>Entry's details are updated</td></tr>
     * <tr><td>'R'</td><td>Replaced (means that the object is first deleted, then
     * another object with the same name is added, all within a single revision)
     * </td><td>Entry's details are updated</td></tr>
     * </table>
     *
     * @param entryCache     DirEntryCache.
     * @param connection     Repository.
     * @param revisionUpdate Update
     */
    protected void updateInternal(final DirEntryCache entryCache, final SVNConnection connection,
            final RevisionUpdate revisionUpdate) {

        final List<LogEntry> revisions = revisionUpdate.getRevisions();
        final int revisionCount = revisions.size();
        final long firstRevision = revisions.get(0).getRevision();
        final long lastRevision = revisions.get(revisionCount - 1).getRevision();

        boolean firstTime = false;
        if (revisionCount > 0 && firstRevision == 1) {
            firstTime = true;
            LOGGER.info("Starting initial cache population for: " + revisionUpdate.getRepositoryName());
        }

        if (lastRevision > entryCache.getLatestCachedRevisionNumber()) {
            // One logEntry is one commit (or revision)
            for (final LogEntry logEntry : revisions) {
                addRevisionToCache(entryCache, connection, logEntry);
            }
            LOGGER.debug("Update completed");

            try {
                entryCache.flush();
            } catch (final CacheException ce) {
                LOGGER.error("Unable to flush cache", ce);
            }
        }

        if (firstTime) {
            LOGGER.info("Cache population completed for: " + revisionUpdate.getRepositoryName());
        }
    }

    private void addRevisionToCache(final DirEntryCache entryCache, final SVNConnection connection,
            final LogEntry logEntry) {
        try {
            final long revision = logEntry.getRevision();
            LOGGER.debug("Applying changes in revision [" + revision + "] to cache");

            final List<DirEntry> entriesToAdd = new ArrayList<DirEntry>();
            final Map<String, DirEntry.Kind> entriesToDelete = new HashMap<String, DirEntry.Kind>();

            for (final ChangedPath entryPath : logEntry.getChangedPaths()) {
                switch (entryPath.getType()) {
                case ADDED:
                    LOGGER.debug("Adding entry to cache: " + entryPath.getPath());
                    doEntryCacheAdd(entriesToAdd, connection, entryPath, revision);
                    break;
                case DELETED:
                    LOGGER.debug("Removing deleted entry from cache: " + entryPath.getPath());
                    doEntryCacheDelete(entriesToDelete, connection, entryPath, revision);
                    break;
                case REPLACED:
                    LOGGER.debug("Replacing entry in cache: " + entryPath.getPath());
                    doEntryCacheReplace(entriesToAdd, entriesToDelete, connection, entryPath, revision);
                    break;
                case MODIFIED:
                    LOGGER.debug("Updating modified entry in cache: " + entryPath.getPath());
                    doEntryCacheModify(entriesToAdd, entriesToDelete, connection, entryPath, revision);
                    break;
                default:
                    throw new RuntimeException(
                            "Unknown log entry type: " + entryPath.getType() + " in rev " + logEntry.getRevision());
                }
            }
            updateCache(entriesToAdd, entriesToDelete, revision, entryCache);
        } catch (SventonException svnex) {
            LOGGER.error("Unable to update entryCache", svnex);
        }
    }

    private void updateCache(final List<DirEntry> entriesToAdd, final Map<String, DirEntry.Kind> entriesToDelete,
            final long revision, DirEntryCache entryCache) {
        entryCache.update(entriesToDelete, entriesToAdd);
        entryCache.setLatestCachedRevisionNumber(revision);
    }

    /**
     * Modifies an entry (file or directory) in the cache.
     *
     * @param entriesToAdd    List of entries to add.
     * @param entriesToDelete List of entries to delete.
     * @param connection      Repository
     * @param logEntryPath    The log entry path
     * @param revision        The log revision
     * @throws SventonException if subversion error occur.
     */
    private void doEntryCacheModify(final List<DirEntry> entriesToAdd,
            final Map<String, DirEntry.Kind> entriesToDelete, final SVNConnection connection,
            final ChangedPath logEntryPath, final long revision) throws SventonException {

        final DirEntry entry = repositoryService.getEntryInfo(connection, logEntryPath.getPath(), revision);
        entriesToDelete.put(entry.getPath(), entry.getKind());
        entriesToAdd.add(entry);
    }

    /**
     * Replaces an entry (file or directory) in the cache.
     *
     * @param entriesToAdd    Entries
     * @param entriesToDelete List of entries to delete.
     * @param connection      Repository
     * @param logEntryPath    The log entry path
     * @param revision        The log revision
     * @throws SventonException if subversion error occur.
     */
    private void doEntryCacheReplace(final List<DirEntry> entriesToAdd,
            final Map<String, DirEntry.Kind> entriesToDelete, final SVNConnection connection,
            final ChangedPath logEntryPath, final long revision) throws SventonException {

        doEntryCacheModify(entriesToAdd, entriesToDelete, connection, logEntryPath, revision);
    }

    /**
     * Deletes an entry (file or directory) from the cache.
     *
     * @param entriesToDelete List of entries to delete.
     * @param connection      Repository
     * @param logEntryPath    The log entry path
     * @param revision        The log revision
     * @throws SventonException if subversion error occur.
     */
    private void doEntryCacheDelete(final Map<String, DirEntry.Kind> entriesToDelete,
            final SVNConnection connection, final ChangedPath logEntryPath, final long revision)
            throws SventonException {

        // Have to find out if deleted entry was a file or directory
        final long previousRevision = revision - 1;

        try {
            final DirEntry deletedEntry = repositoryService.getEntryInfo(connection, logEntryPath.getPath(),
                    previousRevision);
            entriesToDelete.put(logEntryPath.getPath(), deletedEntry.getKind());
        } catch (DirEntryNotFoundException ex) {
            LOGGER.debug("Entry [" + logEntryPath.getPath() + "] does not exist in revision [" + previousRevision
                    + "] - nothing to remove");
        }
    }

    /**
     * Adds an entry (file or directory) to the cache.
     *
     * @param entriesToAdd Entries
     * @param connection   Repository
     * @param logEntryPath The log entry path
     * @param revision     The log revision
     * @throws SventonException if subversion error occur.
     */
    private void doEntryCacheAdd(final List<DirEntry> entriesToAdd, final SVNConnection connection,
            final ChangedPath logEntryPath, final long revision) throws SventonException {

        // Have to find out if added entry was a file or directory
        final DirEntry entry = repositoryService.getEntryInfo(connection, logEntryPath.getPath(), revision);

        // If the entry is a directory and a copyPath exists, the entry is
        // a moved or copied directory (branch). In that case we have to recursively
        // add the entry. If entry is a directory but does not have a copyPath
        // the contents will be added one by one as single entriesToAdd.
        if (entry.getKind() == DirEntry.Kind.DIR && logEntryPath.getCopyPath() != null) {
            // Directory node added
            LOGGER.debug(logEntryPath.getPath() + " is a directory. Doing a recursive add");
            // Add directory contents
            addDirectories(entriesToAdd, connection, logEntryPath.getPath() + "/", revision, repositoryService);
        }
        entriesToAdd.add(entry);
    }

    /**
     * Adds all entries in given path.
     * This method will be recursively called by itself.
     *
     * @param entriesToAdd      List containing entries to add.
     * @param connection        Repository
     * @param path              The path to add.
     * @param revision          Revision
     * @param repositoryService Service
     * @throws SventonException if a Subversion error occurs.
     */
    private void addDirectories(final List<DirEntry> entriesToAdd, final SVNConnection connection,
            final String path, final long revision, final RepositoryService repositoryService)
            throws SventonException {

        final List<DirEntry> entriesList = repositoryService.list(connection, path, revision).getEntries();

        for (final DirEntry entry : entriesList) {
            entriesToAdd.add(entry);
            if (entry.getKind() == DirEntry.Kind.DIR) {
                final String pathToAdd = path + entry.getName() + "/";
                LOGGER.debug("Adding: " + pathToAdd);
                addDirectories(entriesToAdd, connection, pathToAdd, revision, repositoryService);
            }
        }
    }

    /**
     * Sets the connection factory instance.
     *
     * @param connectionFactory Factory instance.
     */
    @Autowired
    public void setConnectionFactory(final SVNConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    /**
     * Sets the repository service instance.
     *
     * @param repositoryService The service instance.
     */
    @Autowired
    public void setRepositoryService(final RepositoryService repositoryService) {
        this.repositoryService = repositoryService;
    }

}