Java tutorial
/* * ==================================================================== * 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; } }