blob: 07a011783044c1efcbaaacff6da8a55489bf53f1 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}