| /******************************************************************************* |
| * Copyright (c) 2008-2014 Chair for Applied Software Engineering, |
| * Technische Universitaet Muenchen. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Otto von Wesendonk - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.emf.emfstore.internal.client.model.controller; |
| |
| import java.text.MessageFormat; |
| import java.util.Date; |
| import java.util.concurrent.Callable; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.emf.emfstore.client.callbacks.ESCommitCallback; |
| import org.eclipse.emf.emfstore.client.exceptions.ESProjectNotSharedException; |
| import org.eclipse.emf.emfstore.client.observer.ESCommitObserver; |
| import org.eclipse.emf.emfstore.client.util.RunESCommand; |
| import org.eclipse.emf.emfstore.internal.client.common.UnknownEMFStoreWorkloadCommand; |
| import org.eclipse.emf.emfstore.internal.client.model.Configuration; |
| import org.eclipse.emf.emfstore.internal.client.model.ESWorkspaceProviderImpl; |
| import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.ServerCall; |
| import org.eclipse.emf.emfstore.internal.client.model.impl.ProjectSpaceBase; |
| import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreClientUtil; |
| import org.eclipse.emf.emfstore.internal.client.model.util.WorkspaceUtil; |
| import org.eclipse.emf.emfstore.internal.common.model.Project; |
| import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil; |
| import org.eclipse.emf.emfstore.internal.common.model.util.SerializationException; |
| import org.eclipse.emf.emfstore.internal.server.conflictDetection.ModelElementIdToEObjectMappingImpl; |
| import org.eclipse.emf.emfstore.internal.server.exceptions.InvalidVersionSpecException; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.AbstractChangePackage; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.BranchVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.LogMessage; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.PrimaryVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.VersioningFactory; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.Versions; |
| import org.eclipse.emf.emfstore.server.exceptions.ESException; |
| import org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException; |
| |
| /** |
| * The controller responsible for performing a commit. |
| * |
| * @author wesendon |
| */ |
| public class CommitController extends ServerCall<PrimaryVersionSpec> { |
| |
| private static final String LOGGING_PREFIX = "COMMIT"; //$NON-NLS-1$ |
| |
| private final String logMessage; |
| private final ESCommitCallback callback; |
| private final BranchVersionSpec branch; |
| |
| /** |
| * Constructor. |
| * |
| * @param projectSpace |
| * the project space whose pending changes should be commited |
| * @param logMessage |
| * a log message documenting the commit |
| * @param callback |
| * an callback that will be called during and at the end of the |
| * commit. May be <code>null</code>. |
| * @param monitor |
| * an {@link IProgressMonitor} that will be used to inform |
| * clients about the commit progress. May be <code>null</code>. |
| */ |
| public CommitController(ProjectSpaceBase projectSpace, String logMessage, ESCommitCallback callback, |
| IProgressMonitor monitor) { |
| this(projectSpace, null, logMessage, callback, monitor); |
| } |
| |
| /** |
| * Branching Constructor. |
| * |
| * @param projectSpace |
| * the project space whose pending changes should be committed |
| * @param branch |
| * Specification of the branch to which the changes should be |
| * committed. |
| * @param logMessage |
| * a log message documenting the commit |
| * @param callback |
| * an callback that will be called during and at the end of the |
| * commit. May be <code>null</code>. |
| * @param monitor |
| * an {@link IProgressMonitor} that will be used to inform |
| * clients about the commit progress. May be <code>null</code>. |
| */ |
| public CommitController(ProjectSpaceBase projectSpace, BranchVersionSpec branch, String logMessage, |
| ESCommitCallback callback, IProgressMonitor monitor) { |
| super(projectSpace); |
| this.branch = branch; |
| this.logMessage = logMessage == null ? Messages.CommitController_NoMessage : logMessage; |
| this.callback = callback == null ? ESCommitCallback.NOCALLBACK : callback; |
| setProgressMonitor(monitor); |
| } |
| |
| @Override |
| protected PrimaryVersionSpec run() throws ESException { |
| return commit(logMessage, branch); |
| } |
| |
| private PrimaryVersionSpec commit(final String logMessage, final BranchVersionSpec branch) |
| throws InvalidVersionSpecException, ESUpdateRequiredException, ESException { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "CommitController started", getProjectSpace(), branch, //$NON-NLS-1$ |
| getUsersession()); |
| |
| if (!getProjectSpace().isShared()) { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Stopping commit because project is not shared", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| throw new ESProjectNotSharedException(); |
| } |
| |
| getProgressMonitor().beginTask(Messages.CommitController_CommitingChanges, 100); |
| getProgressMonitor().worked(1); |
| getProgressMonitor().subTask(Messages.CommitController_CheckingChanges); |
| |
| // check if there are any changes. Branch commits are allowed with no changes, whereas normal commits are not. |
| if (!getProjectSpace().isDirty() && branch == null) { |
| callback.noLocalChanges(getProjectSpace().toAPI()); |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Stopping commit because no changes and no new branch", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| return getProjectSpace().getBaseVersion(); |
| } |
| |
| getProjectSpace().cleanCutElements(); |
| |
| getProgressMonitor().subTask(Messages.CommitController_ResolvingNewVersion); |
| |
| checkForCommitPreconditions(branch, getProgressMonitor()); |
| |
| getProgressMonitor().worked(10); |
| getProgressMonitor().subTask(Messages.CommitController_GatheringChanges); |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Gathering changes...", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| |
| final AbstractChangePackage localChangePackage = getProjectSpace().getLocalChangePackage(); |
| |
| setLogMessage(logMessage, localChangePackage); |
| |
| ESWorkspaceProviderImpl.getObserverBus().notify(ESCommitObserver.class) |
| .inspectChanges(getProjectSpace().toAPI(), localChangePackage.toAPI(), getProgressMonitor()); |
| |
| final ModelElementIdToEObjectMappingImpl idToEObjectMapping = new ModelElementIdToEObjectMappingImpl( |
| getProjectSpace().getProject(), localChangePackage); |
| |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Gathering changes... done", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| |
| getProgressMonitor().subTask(Messages.CommitController_PresentingChanges); |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Presenting changes...", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| if (!callback.inspectChanges(getProjectSpace().toAPI(), |
| localChangePackage.toAPI(), |
| idToEObjectMapping.toAPI()) |
| || getProgressMonitor().isCanceled()) { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Commit vetoed by ESCommitCallback/ProgressMonitor", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| |
| return getProjectSpace().getBaseVersion(); |
| } |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Presenting changes... done", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| |
| getProgressMonitor().subTask(Messages.CommitController_SendingFilesToServer); |
| // TODO reimplement with ObserverBus and think about subtasks for commit |
| getProjectSpace().getFileTransferManager().uploadQueuedFiles(getProgressMonitor()); |
| getProgressMonitor().worked(30); |
| |
| getProgressMonitor().subTask(Messages.CommitController_SendingChangesToServer); |
| |
| // check again if an update is required |
| final boolean updatePerformed = checkForCommitPreconditions(branch, getProgressMonitor()); |
| // present changes again if update was performed |
| if (updatePerformed) { |
| getProgressMonitor().subTask("Presenting Changes"); //$NON-NLS-1$ |
| if (!callback.inspectChanges(getProjectSpace().toAPI(), |
| localChangePackage.toAPI(), |
| idToEObjectMapping.toAPI()) |
| || getProgressMonitor().isCanceled()) { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, |
| "Stopping commit because updated project was vetoed.", getProjectSpace(), branch, getUsersession()); //$NON-NLS-1$ |
| return getProjectSpace().getBaseVersion(); |
| } |
| } |
| |
| return commitAfterUpdate(branch, localChangePackage); |
| } |
| |
| private PrimaryVersionSpec commitAfterUpdate(final BranchVersionSpec branch, |
| final AbstractChangePackage localChangePackage) throws ESException { |
| final PrimaryVersionSpec newBaseVersion = performCommit(branch, localChangePackage); |
| |
| // TODO reimplement with ObserverBus and think about subtasks for commit |
| getProgressMonitor().worked(35); |
| getProgressMonitor().subTask("Sending files to server"); //$NON-NLS-1$ |
| |
| getProjectSpace().getFileTransferManager().uploadQueuedFiles(getProgressMonitor()); |
| |
| getProgressMonitor().worked(30); |
| getProgressMonitor().subTask(Messages.CommitController_ComputingChecksum); |
| |
| handleChecksumProcessing(newBaseVersion); |
| |
| getProgressMonitor().subTask(Messages.CommitController_FinalizingCommit); |
| |
| RunESCommand.run(new Callable<Void>() { |
| public Void call() throws Exception { |
| getProjectSpace().setBaseVersion(newBaseVersion); |
| getProjectSpace().getLocalChangePackage().clear(); |
| getProjectSpace().setMergedVersion(null); |
| getProjectSpace().updateDirtyState(); |
| return null; |
| } |
| }); |
| |
| ESWorkspaceProviderImpl.getObserverBus().notify(ESCommitObserver.class) |
| .commitCompleted(getProjectSpace().toAPI(), newBaseVersion.toAPI(), getProgressMonitor()); |
| |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Commit successful", getProjectSpace(), branch, //$NON-NLS-1$ |
| getUsersession()); |
| return newBaseVersion; |
| } |
| |
| private void handleChecksumProcessing(final PrimaryVersionSpec newBaseVersion) throws ESException { |
| boolean validChecksum = true; |
| try { |
| validChecksum = performChecksumCheck(newBaseVersion, getProjectSpace().getProject()); |
| } catch (final SerializationException exception) { |
| WorkspaceUtil.logWarning(MessageFormat.format(Messages.CommitController_ChecksumComputationFailed, |
| getProjectSpace().getProjectName()), exception); |
| } |
| |
| if (!validChecksum) { |
| getProgressMonitor().subTask(Messages.CommitController_InvalidChecksum); |
| final boolean errorHandled = Configuration.getClientBehavior().getChecksumErrorHandler() |
| .execute(getProjectSpace().toAPI(), newBaseVersion.toAPI(), getProgressMonitor()); |
| if (!errorHandled) { |
| throw new ESException(Messages.CommitController_CommitCancelled_InvalidChecksum); |
| } |
| } |
| } |
| |
| private PrimaryVersionSpec performCommit(final BranchVersionSpec branch, final AbstractChangePackage changePackage) |
| throws ESException { |
| |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Perform commit..", getProjectSpace(), branch, //$NON-NLS-1$ |
| getUsersession()); |
| // Branching case: branch specifier added |
| final PrimaryVersionSpec newBaseVersion = new UnknownEMFStoreWorkloadCommand<PrimaryVersionSpec>( |
| getProgressMonitor()) { |
| @Override |
| public PrimaryVersionSpec run(IProgressMonitor monitor) throws ESException { |
| return getConnectionManager().createVersion( |
| getUsersession().getSessionId(), |
| getProjectSpace().getProjectId(), |
| getProjectSpace().getBaseVersion(), |
| changePackage, |
| branch, |
| getProjectSpace().getMergedVersion(), |
| changePackage.getLogMessage()); |
| } |
| }.execute(); |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Perform commit.. done", getProjectSpace(), branch, //$NON-NLS-1$ |
| getUsersession()); |
| return newBaseVersion; |
| } |
| |
| private void setLogMessage(final String logMessage, final AbstractChangePackage changePackage) { |
| RunESCommand.run(new Callable<Void>() { |
| public Void call() throws Exception { |
| final LogMessage logMsg = VersioningFactory.eINSTANCE.createLogMessage(); |
| logMsg.setMessage(logMessage); |
| logMsg.setClientDate(new Date()); |
| logMsg.setAuthor(getProjectSpace().getUsersession().getUsername()); |
| changePackage.setLogMessage(logMsg); |
| return null; |
| } |
| }); |
| } |
| |
| private boolean performChecksumCheck(PrimaryVersionSpec newBaseVersion, Project project) |
| throws SerializationException { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Perform checksum check..", getProjectSpace(), branch, //$NON-NLS-1$ |
| getUsersession()); |
| |
| if (Configuration.getClientBehavior().isChecksumCheckActive()) { |
| final long computedChecksum = ModelUtil.computeChecksum(project); |
| EMFStoreClientUtil.logProjectDetails( |
| LOGGING_PREFIX, MessageFormat.format("Computed Checksum: {0} , ProjectState Checksum: {1}", //$NON-NLS-1$ |
| computedChecksum, newBaseVersion.getProjectStateChecksum()), |
| getProjectSpace(), branch, getUsersession()); |
| return computedChecksum == newBaseVersion.getProjectStateChecksum(); |
| } |
| |
| return true; |
| } |
| |
| private boolean checkForCommitPreconditions(final BranchVersionSpec branch, IProgressMonitor monitor) |
| throws InvalidVersionSpecException, |
| ESException, ESUpdateRequiredException { |
| if (branch != null) { |
| // check branch conditions |
| if (StringUtils.isEmpty(branch.getBranch())) { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Stopping commit because of empty branch name", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| throw new InvalidVersionSpecException(Messages.CommitController_EmptyBranchName); |
| } |
| PrimaryVersionSpec potentialBranch = null; |
| try { |
| potentialBranch = getProjectSpace().resolveVersionSpec(branch, monitor); |
| } catch (final InvalidVersionSpecException e) { |
| // branch doesn't exist, create. |
| } |
| if (potentialBranch != null) { |
| throw new InvalidVersionSpecException(Messages.CommitController_BranchAlreadyExists); |
| } |
| |
| } else { |
| // check if we need to update first |
| final PrimaryVersionSpec resolvedVersion = getProjectSpace() |
| .resolveVersionSpec( |
| Versions.createHEAD(getProjectSpace().getBaseVersion()), monitor); |
| if (!getProjectSpace().getBaseVersion().equals(resolvedVersion)) { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Update required", getProjectSpace(), branch, //$NON-NLS-1$ |
| getUsersession()); |
| if (!callback.baseVersionOutOfDate(getProjectSpace().toAPI(), getProgressMonitor())) { |
| EMFStoreClientUtil.logProjectDetails(LOGGING_PREFIX, "Stopping commit because update required", //$NON-NLS-1$ |
| getProjectSpace(), branch, getUsersession()); |
| throw new ESUpdateRequiredException(); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| } |