org.sventon.repository.RevisionObservableImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sventon.repository.RevisionObservableImpl.java

Source

/*
 * ====================================================================
 * Copyright (c) 2005-2010 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.repository;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.sventon.RepositoryConnectionFactory;
import org.sventon.appl.Application;
import org.sventon.appl.ObjectCacheManager;
import org.sventon.appl.RepositoryConfiguration;
import org.sventon.cache.objectcache.ObjectCache;
import org.sventon.model.RepositoryName;
import org.sventon.repository.observer.RevisionObserver;
import org.sventon.service.RepositoryService;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.io.SVNRepository;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;

/**
 * Class to monitor repository changes.
 * During check, the latest revision number is fetched from the
 * repository and compared to the observable's latest revision.
 * If it differs, the revision delta will be fetched and published
 * to registered observers.
 *
 * @author jesper@user.berlios.de
 */
public final class RevisionObservableImpl extends Observable implements RevisionObservable {

    /**
     * The logging instance.
     */
    private final Log logger = LogFactory.getLog(getClass());

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

    /**
     * Object cache manager instance.
     * Used to get/put info regarding repository URL and last observed revision.
     */
    private ObjectCacheManager objectCacheManager;

    /**
     * Object cache key, <code>lastCachedLogRevision</code>.
     */
    private static final String LAST_UPDATED_REVISION_CACHE_KEY = "lastCachedRevision";

    /**
     * Cache format version. If incremented, caches will be scratched and rebuilt.
     */
    private static final int CACHE_FORMAT_VERSION = 1;

    /**
     * Maximum number of revisions to get each update.
     */
    private int maxRevisionCountPerUpdate;

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

    /**
     * Repository factory.
     */
    private RepositoryConnectionFactory repositoryConnectionFactory;

    /**
     * Constructor.
     *
     * @param observers List of observers to add
     */
    public RevisionObservableImpl(final List<RevisionObserver> observers) {
        logger.info("Starting revision observable");

        for (final RevisionObserver revisionObserver : observers) {
            logger.debug("Adding observer: " + revisionObserver.getClass().getName());
            addObserver(revisionObserver);
        }
    }

    /**
     * Sets the maximum number of revisions to get (and update) each time.
     * If will hopefully decrease the memory consumption during cache loading
     * on big repositories.
     *
     * @param count Max revisions per update.
     */
    public void setMaxRevisionCountPerUpdate(final int count) {
        this.maxRevisionCountPerUpdate = count;
    }

    /**
     * Sets the application.
     *
     * @param application Application
     */
    @Autowired
    public void setApplication(final Application application) {
        this.application = application;
    }

    /**
     * Sets the object cache manager instance.
     *
     * @param objectCacheManager The cache manager instance.
     */
    @Autowired
    public void setObjectCacheManager(final ObjectCacheManager objectCacheManager) {
        this.objectCacheManager = objectCacheManager;
    }

    /**
     * Update.
     *
     * @param name             The Repository name
     * @param repository       Repository to use for update.
     * @param objectCache      ObjectCache instance to read/write information about last observed revisions.
     * @param flushAfterUpdate If <tt>true</tt>, caches will be flushed after update.
     */
    protected void update(final RepositoryName name, final SVNRepository repository, final ObjectCache objectCache,
            final boolean flushAfterUpdate) {

        try {
            final long headRevision = repositoryService.getLatestRevision(repository);
            final String lastUpdatedKeyName = getLastUpdatedKeyName(name);
            Long lastUpdatedRevision = (Long) objectCache.get(lastUpdatedKeyName);

            boolean clearCacheBeforeUpdate = false;

            if (lastUpdatedRevision == null) {
                logger.info(
                        "No record about previously fetched revisions exists - fetching all revisions for repository: "
                                + name);
                clearCacheBeforeUpdate = true;
                lastUpdatedRevision = 0L;
            }

            handleSuspectedUrlChange(lastUpdatedRevision, headRevision);

            if (headRevision > lastUpdatedRevision) {
                long revisionsLeftToFetchCount = headRevision - lastUpdatedRevision;
                logger.debug(
                        "About to fetch [" + revisionsLeftToFetchCount + "] revisions from repository: " + name);

                do {
                    final long fromRevision = lastUpdatedRevision + 1;
                    final long toRevision = revisionsLeftToFetchCount > maxRevisionCountPerUpdate
                            ? lastUpdatedRevision + maxRevisionCountPerUpdate
                            : headRevision;

                    final List<SVNLogEntry> logEntries = new ArrayList<SVNLogEntry>();
                    logEntries.addAll(
                            repositoryService.getRevisionsFromRepository(repository, fromRevision, toRevision));
                    logger.debug("Read [" + logEntries.size() + "] revision(s) from repository: " + name);
                    setChanged();
                    logger.info(createNotificationLogMessage(fromRevision, toRevision, logEntries.size()));
                    notifyObservers(new RevisionUpdate(name, logEntries, flushAfterUpdate, clearCacheBeforeUpdate));
                    lastUpdatedRevision = toRevision;
                    logger.debug("Updating 'lastUpdatedRevision' to: " + lastUpdatedRevision);
                    objectCache.put(lastUpdatedKeyName, lastUpdatedRevision);
                    objectCache.flush();
                    clearCacheBeforeUpdate = false;
                    revisionsLeftToFetchCount -= logEntries.size();
                } while (revisionsLeftToFetchCount > 0);
            }
        } catch (SVNException svnex) {
            logger.warn("Exception: " + svnex.getMessage());
            logger.debug("Exception [" + svnex.getErrorMessage().getErrorCode().toString() + "]", svnex);
        }
    }

    private String getLastUpdatedKeyName(RepositoryName name) {
        return LAST_UPDATED_REVISION_CACHE_KEY + "_" + CACHE_FORMAT_VERSION + "_" + name;
    }

    /**
     * Sanity check of revision numbers.
     *
     * @param lastUpdatedRevision Last updated revision number
     * @param headRevision        Current head revision number.
     * @throws IllegalStateException if last updated revision is greater than head revision.
     */
    private void handleSuspectedUrlChange(final long lastUpdatedRevision, long headRevision) {
        if (headRevision < lastUpdatedRevision) {
            final String errorMessage = "Repository HEAD revision (" + headRevision + ") is lower than last cached"
                    + " revision. The repository URL has probably been changed. Delete all cache files from the temp directory"
                    + " and restart sventon.";
            logger.error(errorMessage);
            throw new IllegalStateException(errorMessage);
        }
    }

    /**
     * Creates an informative string for logging observer notifications.
     *
     * @param fromRevision    From revision
     * @param toRevision      To revision
     * @param logEntriesCount Number of log entries.
     * @return String intended for logging.
     */
    private String createNotificationLogMessage(long fromRevision, long toRevision, int logEntriesCount) {
        final StringBuffer notification = new StringBuffer();
        notification.append("Notifying observers about [");
        notification.append(logEntriesCount);
        notification.append("] revisions [");
        notification.append(fromRevision);
        notification.append("-");
        notification.append(toRevision);
        notification.append("]");
        return notification.toString();
    }

    /**
     * {@inheritDoc}
     */
    public void update(final RepositoryName repositoryName, final boolean flushAfterUpdate) {
        if (application.isConfigured()) {
            final RepositoryConfiguration configuration = application.getRepositoryConfiguration(repositoryName);

            if (configuration.isCacheUsed() && !application.isUpdating(repositoryName)) {
                application.setUpdatingCache(repositoryName, true);
                SVNRepository repository = null;
                try {
                    repository = repositoryConnectionFactory.createConnection(configuration.getName(),
                            configuration.getSVNURL(), configuration.getCacheCredentials());
                    final ObjectCache objectCache = objectCacheManager.getCache(repositoryName);
                    update(repositoryName, repository, objectCache, flushAfterUpdate);
                } catch (final Exception ex) {
                    logger.warn("Unable to establish repository connection", ex);
                } finally {
                    if (repository != null) {
                        repository.closeSession();
                    }
                    application.setUpdatingCache(repositoryName, false);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void updateAll() {
        if (application.isConfigured()) {
            for (final RepositoryName repositoryName : application.getRepositoryNames()) {
                update(repositoryName, true);
            }
        }
    }

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

    /**
     * Sets the repository connection factory instance.
     *
     * @param repositoryConnectionFactory Factory instance.
     */
    @Autowired
    public void setRepositoryConnectionFactory(final RepositoryConnectionFactory repositoryConnectionFactory) {
        this.repositoryConnectionFactory = repositoryConnectionFactory;
    }
}