/** * This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details. */ package edu.illinois.codingspectator.monitor.ui.submission; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.equinox.p2.core.UIServices.AuthenticationInfo; import org.eclipse.equinox.security.storage.StorageException; import org.tmatesoft.svn.core.SVNException; import edu.illinois.codingspectator.data.CodingSpectatorDataPlugin; import edu.illinois.codingspectator.monitor.core.authentication.AuthenticationProvider; import edu.illinois.codingspectator.monitor.core.submission.SVNManager; import edu.illinois.codingspectator.monitor.core.submission.SubmitterListener; import edu.illinois.codingspectator.monitor.core.submission.URLManager; import edu.illinois.codingspectator.monitor.ui.Activator; import edu.illinois.codingspectator.monitor.ui.AuthenticationPrompter; import edu.illinois.codingspectator.monitor.ui.ExceptionUtil; import edu.illinois.codingspectator.monitor.ui.prefs.PrefsFacade; /** * * A Submitter is responsible for submitting the recorded refactoring logs. It gathers those logs * from a directory, imports it to some repository. * * @author Mohsen Vakilian * @author nchen * @author Stas Negara * */ public class Submitter { public static final String WATCHED_FOLDER= CodingSpectatorDataPlugin.getStorageLocation().toOSString(); private SVNManager svnManager; private AuthenticationProvider authenticationProvider; private Collection<SubmitterListener> submitterListeners= new ArrayList<SubmitterListener>(); private String uuid; public Submitter() { } public Submitter(AuthenticationProvider provider) { this.authenticationProvider= provider; } public enum AuthenticanResult { CANCELED_AUTHENTICATION, WRONG_AUTHENTICATION, OK } public String getUUID() { return uuid; } public AuthenticanResult authenticate() throws InitializationException { AuthenticationProvider prompter= getAuthenticationPrompterLazily(); AuthenticationInfo authenticationInfo= prompter.findUsernamePassword(); if (isCanceled(authenticationInfo)) { return AuthenticanResult.CANCELED_AUTHENTICATION; } uuid= PrefsFacade.getInstance().getAndSetUUIDLazily(); URLManager urlManager= new URLManager(prompter.getRepositoryURL(), authenticationInfo.getUserName(), uuid); svnManager= new SVNManager(urlManager, WATCHED_FOLDER, authenticationInfo.getUserName(), authenticationInfo.getPassword()); if (!svnManager.isAuthenticationInformationValid()) { return AuthenticanResult.WRONG_AUTHENTICATION; } try { prompter.saveAuthenticationInfo(authenticationInfo); } catch (StorageException e) { throw new InitializationException(e); } catch (IOException e) { throw new InitializationException(e); } return AuthenticanResult.OK; } private boolean isCanceled(AuthenticationInfo authenticationInfo) { return authenticationInfo == null; } private AuthenticationProvider getAuthenticationPrompterLazily() { if (authenticationProvider == null) { authenticationProvider= new AuthenticationPrompter(); } return authenticationProvider; } public void submit() throws SubmissionException { boolean submissionSucceeded= false; try { submitterListeners= lookupExtensions(); notifyPreSubmit(); resolveLocalAndRemoteDataMismatches(); doSVNSubmit(); submissionSucceeded= true; } catch (Throwable e1) { try { removeLocalAndRemoteData("Deleted the remote data because the submission failed with the following exception:\n" + ExceptionUtil.getStackTrace(e1)); doSVNSubmit(); submissionSucceeded= true; } catch (Throwable e2) { throw new SubmissionException(e2); } } finally { notifyPostSubmit(submissionSucceeded); } } /** * The following method is useful for testing purposes. This methods notifies all * SubmitterListeners. Since some of the listeners transfer their data to the watched folder * when they get notified, this method gathers all the collected data into the watched folder. */ public void notifyListeners() { submitterListeners= lookupExtensions(); notifyPreSubmit(); notifyPreCommit(); notifyPostSubmit(true); } private void doSVNSubmit() throws SVNException { svnManager.doImportIfNecessary(); svnManager.doCleanupIfPossible(); svnManager.doCheckout(); svnManager.doAdd(); notifyPreCommit(); svnManager.doCommit(); updateLocalRevisionNumbers(); } /** * * This method is used by an automated test. * * @return * @throws SVNException */ public boolean doLocalAndRemoteDataMatch() throws SVNException { return svnManager.isWatchedFolderInRepository() && svnManager.isWorkingDirectoryValid() && !svnManager.isLocalWorkCopyOutdated(); } private void resolveLocalAndRemoteDataMismatches() throws SVNException, CoreException { if (svnManager.isWatchedFolderInRepository()) { if (!svnManager.isWorkingDirectoryValid()) { removeRemoteData("Deleted the remote data because the personal worskpace existed on the remote repository, but, the local working copy was invalid."); } else if (svnManager.isLocalWorkCopyOutdated()) { removeLocalAndRemoteData("Deleted the remote data because the local working copy was outdated."); } } else { svnManager.removeSVNMetaData(); } } private void removeRemoteData(String svnMessage) throws SVNException { svnManager.doDelete(svnMessage); } private void removeLocalAndRemoteData(String svnMessage) throws CoreException, SVNException { svnManager.removeSVNMetaData(); removeRemoteData(svnMessage); } private void updateLocalRevisionNumbers() throws SVNException { svnManager.doUpdate(); } private Collection<SubmitterListener> lookupExtensions() { String extensionPointId= "edu.illinois.codingspectator.monitor.core.submitter"; IConfigurationElement[] config= Platform.getExtensionRegistry().getConfigurationElementsFor(extensionPointId); Collection<SubmitterListener> submitterListeners= new ArrayList<SubmitterListener>(); try { for (IConfigurationElement e : config) { Object o= e.createExecutableExtension("class"); submitterListeners.add((SubmitterListener)o); } } catch (CoreException e) { Activator.getDefault().createErrorStatus(String.format("Failed to lookup extensions for %s.", extensionPointId), e); } return submitterListeners; } private void notifyPreSubmit() { for (final SubmitterListener submitterListener : submitterListeners) { SafeRunner.run(new ISafeRunnable() { @Override public void run() throws Exception { submitterListener.preSubmit(); } @Override public void handleException(Throwable exception) { } }); } } private void notifyPreCommit() { for (final SubmitterListener submitterListener : submitterListeners) { SafeRunner.run(new ISafeRunnable() { @Override public void run() throws Exception { submitterListener.preCommit(); } @Override public void handleException(Throwable exception) { } }); } } private void notifyPostSubmit(final boolean succeeded) { for (final SubmitterListener submitterListener : submitterListeners) { SafeRunner.run(new ISafeRunnable() { @Override public void run() throws Exception { submitterListener.postSubmit(succeeded); } @Override public void handleException(Throwable exception) { } }); } } /** * @return true if it can obtain a valid credential or false if the user has forcibly canceled * @throws InitializationException */ public boolean promptUntilValidCredentialsOrCanceled() throws InitializationException { AuthenticanResult authenticanResult; do { authenticanResult= authenticate(); if (authenticanResult == AuthenticanResult.CANCELED_AUTHENTICATION) { return false; } } while (authenticanResult != AuthenticanResult.OK); return true; } @SuppressWarnings("serial") public static abstract class SubmitterException extends Exception { public SubmitterException() { super(); } public SubmitterException(Throwable e) { super(e); } } @SuppressWarnings("serial") public static class InitializationException extends SubmitterException { public InitializationException(Throwable e) { super(e); } public InitializationException(SVNException e) { super(e); } } @SuppressWarnings("serial") public static class SubmissionException extends SubmitterException { public SubmissionException(Throwable e) { super(e); } } }