blob: b43b3b81597dd59b982eacab8e8ed84fdbdeee82 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2011 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:
******************************************************************************/
package org.eclipse.emf.emfstore.server.core.subinterfaces;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.emfstore.common.model.Project;
import org.eclipse.emf.emfstore.common.model.impl.ProjectImpl;
import org.eclipse.emf.emfstore.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.server.EmfStoreController;
import org.eclipse.emf.emfstore.server.ServerConfiguration;
import org.eclipse.emf.emfstore.server.core.AbstractEmfstoreInterface;
import org.eclipse.emf.emfstore.server.core.AbstractSubEmfstoreInterface;
import org.eclipse.emf.emfstore.server.core.helper.HistoryCache;
import org.eclipse.emf.emfstore.server.exceptions.BaseVersionOutdatedException;
import org.eclipse.emf.emfstore.server.exceptions.EmfStoreException;
import org.eclipse.emf.emfstore.server.exceptions.FatalEmfStoreException;
import org.eclipse.emf.emfstore.server.exceptions.InvalidVersionSpecException;
import org.eclipse.emf.emfstore.server.exceptions.StorageException;
import org.eclipse.emf.emfstore.server.model.ProjectHistory;
import org.eclipse.emf.emfstore.server.model.ProjectId;
import org.eclipse.emf.emfstore.server.model.accesscontrol.ACUser;
import org.eclipse.emf.emfstore.server.model.versioning.ChangePackage;
import org.eclipse.emf.emfstore.server.model.versioning.DateVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.HeadVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.LogMessage;
import org.eclipse.emf.emfstore.server.model.versioning.PrimaryVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.TagVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.Version;
import org.eclipse.emf.emfstore.server.model.versioning.VersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.VersioningFactory;
/**
* This subinterfaces implements all version related functionality for the
* {@link org.eclipse.emf.emfstore.server.core.EmfStoreImpl} interface.
*
* @author wesendon
*/
public class VersionSubInterfaceImpl extends AbstractSubEmfstoreInterface {
private HistoryCache historyCache;
/**
* Default constructor.
*
* @param parentInterface
* parent interface
* @throws FatalEmfStoreException
* in case of failure
*/
public VersionSubInterfaceImpl(AbstractEmfstoreInterface parentInterface) throws FatalEmfStoreException {
super(parentInterface);
}
/**
* {@inheritDoc}
*
* @throws FatalEmfStoreException
* in case of failure
* @see org.eclipse.emf.emfstore.server.core.AbstractSubEmfstoreInterface#initSubInterface()
*/
@Override
public void initSubInterface() throws FatalEmfStoreException {
super.initSubInterface();
historyCache = EmfStoreController.getInstance().getHistoryCache();
}
/**
* Resolves a versionSpec and delivers the corresponding primary
* versionSpec.
*
* @param projectId
* project id
* @param versionSpec
* versionSpec
* @return primary versionSpec
* @throws EmfStoreException
* if versionSpec can't be resolved or other failure
*/
public PrimaryVersionSpec resolveVersionSpec(ProjectId projectId, VersionSpec versionSpec) throws EmfStoreException {
synchronized (getMonitor()) {
ProjectHistory projectHistory = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId);
// PrimaryVersionSpec
if (versionSpec instanceof PrimaryVersionSpec && 0 <= ((PrimaryVersionSpec) versionSpec).getIdentifier()
&& ((PrimaryVersionSpec) versionSpec).getIdentifier() < projectHistory.getVersions().size()) {
return ((PrimaryVersionSpec) versionSpec);
// HeadVersionSpec
} else if (versionSpec instanceof HeadVersionSpec) {
return EcoreUtil.copy(getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId)
.getLastVersion().getPrimarySpec());
// DateVersionSpec
} else if (versionSpec instanceof DateVersionSpec) {
for (Version version : projectHistory.getVersions()) {
LogMessage logMessage = version.getLogMessage();
if (logMessage == null || logMessage.getDate() == null) {
continue;
}
if (((DateVersionSpec) versionSpec).getDate().before(logMessage.getDate())) {
Version previousVersion = version.getPreviousVersion();
if (previousVersion == null) {
return VersioningFactory.eINSTANCE.createPrimaryVersionSpec();
}
return previousVersion.getPrimarySpec();
}
}
return projectHistory.getLastVersion().getPrimarySpec();
// TagVersionSpec
} else if (versionSpec instanceof TagVersionSpec) {
for (Version version : projectHistory.getVersions()) {
for (TagVersionSpec tag : version.getTagSpecs()) {
if (((TagVersionSpec) versionSpec).equals(tag)) {
return EcoreUtil.copy(version.getPrimarySpec());
}
}
}
throw new InvalidVersionSpecException();
} else {
throw new InvalidVersionSpecException();
}
}
}
/**
* {@inheritDoc}
*
* @param user
*/
public PrimaryVersionSpec createVersion(ProjectId projectId, PrimaryVersionSpec baseVersionSpec,
ChangePackage changePackage, LogMessage logMessage, ACUser user) throws EmfStoreException {
synchronized (getMonitor()) {
long currentTimeMillis = System.currentTimeMillis();
ProjectHistory projectHistory = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId);
List<Version> versions = projectHistory.getVersions();
// OW: check here if base version is valid at all
if (versions.size() - 1 != baseVersionSpec.getIdentifier()) {
throw new BaseVersionOutdatedException();
}
PrimaryVersionSpec newVersionSpec = VersioningFactory.eINSTANCE.createPrimaryVersionSpec();
newVersionSpec.setIdentifier(baseVersionSpec.getIdentifier() + 1);
Version newVersion = VersioningFactory.eINSTANCE.createVersion();
Version previousHeadVersion = versions.get(versions.size() - 1);
Project newProjectState = ((ProjectImpl) previousHeadVersion.getProjectState()).copy();
changePackage.apply(newProjectState);
newVersion.setProjectState(newProjectState);
newVersion.setChanges(changePackage);
logMessage.setDate(new Date());
logMessage.setAuthor(user.getName());
newVersion.setLogMessage(logMessage);
newVersion.setPrimarySpec(newVersionSpec);
newVersion.setNextVersion(null);
newVersion.setPreviousVersion(previousHeadVersion);
versions.add(newVersion);
// try to save
try {
try {
getResourceHelper().createResourceForProject(newProjectState, newVersion.getPrimarySpec(),
projectHistory.getProjectId());
getResourceHelper().createResourceForChangePackage(changePackage, newVersion.getPrimarySpec(),
projectId);
getResourceHelper().createResourceForVersion(newVersion, projectHistory.getProjectId());
} catch (FatalEmfStoreException e) {
// try to roll back
previousHeadVersion.setNextVersion(null);
versions.remove(newVersion);
// TODO: OW: why do we need to save here, can we remove? do test!!
save(previousHeadVersion);
save(projectHistory);
throw new StorageException(StorageException.NOSAVE);
}
// delete projectstate from last revision depending on
// persistence
// policy
handleOldProjectState(projectId, previousHeadVersion);
save(previousHeadVersion);
save(projectHistory);
// update history cache
historyCache.addVersionToCache(projectId, newVersion);
} catch (FatalEmfStoreException e) {
// roll back failed
EmfStoreController.getInstance().shutdown(e);
throw new EmfStoreException("Shutting down server.");
}
ModelUtil.logInfo("Total time for commit: " + (System.currentTimeMillis() - currentTimeMillis));
return newVersionSpec;
}
}
/**
* {@inheritDoc}
*/
public PrimaryVersionSpec createVersionForProject(ProjectId projectId, PrimaryVersionSpec baseVersionSpec,
ChangePackage changePackage, LogMessage logMessage) throws EmfStoreException {
synchronized (getMonitor()) {
long currentTimeMillis = System.currentTimeMillis();
ProjectHistory projectHistory = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId);
List<Version> versions = projectHistory.getVersions();
// OW: check here if base version is valid at all
if (versions.size() - 1 != baseVersionSpec.getIdentifier()) {
throw new BaseVersionOutdatedException();
}
PrimaryVersionSpec newVersionSpec = VersioningFactory.eINSTANCE.createPrimaryVersionSpec();
newVersionSpec.setIdentifier(baseVersionSpec.getIdentifier() + 1);
Version newVersion = VersioningFactory.eINSTANCE.createVersion();
Version previousHeadVersion = versions.get(versions.size() - 1);
Project newProjectState = ModelUtil.clone(previousHeadVersion.getProjectState());
changePackage.apply(newProjectState);
newVersion.setProjectState(newProjectState);
newVersion.setChanges(changePackage);
logMessage.setDate(new Date());
newVersion.setLogMessage(logMessage);
newVersion.setPrimarySpec(newVersionSpec);
newVersion.setNextVersion(null);
newVersion.setPreviousVersion(previousHeadVersion);
versions.add(newVersion);
// try to save
try {
try {
getResourceHelper().createResourceForProject(newProjectState, newVersion.getPrimarySpec(),
projectHistory.getProjectId());
getResourceHelper().createResourceForChangePackage(changePackage, newVersion.getPrimarySpec(),
projectId);
getResourceHelper().createResourceForVersion(newVersion, projectHistory.getProjectId());
} catch (FatalEmfStoreException e) {
// try to roll back
previousHeadVersion.setNextVersion(null);
versions.remove(newVersion);
// OW: why do we need to save here, can we remove? do test!!
save(previousHeadVersion);
save(projectHistory);
throw new StorageException(StorageException.NOSAVE);
}
// delete projectstate from last revision depending on
// persistence
// policy
handleOldProjectState(projectId, previousHeadVersion);
save(previousHeadVersion);
save(projectHistory);
// update history cache
historyCache.addVersionToCache(projectId, newVersion);
} catch (FatalEmfStoreException e) {
// roll back failed
EmfStoreController.getInstance().shutdown(e);
throw new EmfStoreException("Shutting down server.");
}
ModelUtil.logInfo("Total time for commit: " + (System.currentTimeMillis() - currentTimeMillis));
return newVersionSpec;
}
}
/**
* Deletes projectstate from last revision depending on persistence policy.
*
* @param projectId
* project id
* @param previousHeadVersion
* last head version
*/
private void handleOldProjectState(ProjectId projectId, Version previousHeadVersion) {
String property = ServerConfiguration.getProperties().getProperty(
ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE,
ServerConfiguration.PROJECTSPACE_VERSION_PERSISTENCE_DEFAULT);
if (property.equals(ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS)) {
int x = getResourceHelper().getXFromPolicy(
ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS_X,
ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS_X_DEFAULT, false);
// always save projecstate of first version
int lastVersion = previousHeadVersion.getPrimarySpec().getIdentifier();
if (lastVersion != 0 && lastVersion % x != 0) {
previousHeadVersion.setProjectState(null);
getResourceHelper().deleteProjectState(projectId, previousHeadVersion.getPrimarySpec().getIdentifier());
}
} else {
previousHeadVersion.setProjectState(null);
getResourceHelper().deleteProjectState(projectId, previousHeadVersion.getPrimarySpec().getIdentifier());
}
}
/**
* {@inheritDoc}
*/
public List<ChangePackage> getChanges(ProjectId projectId, VersionSpec source, VersionSpec target)
throws EmfStoreException {
synchronized (getMonitor()) {
PrimaryVersionSpec resolvedSource = resolveVersionSpec(projectId, source);
PrimaryVersionSpec resolvedTarget = resolveVersionSpec(projectId, target);
// if target and source are equal return empty list
if (resolvedSource.getIdentifier() == resolvedTarget.getIdentifier()) {
return new ArrayList<ChangePackage>();
}
boolean updateForward = resolvedTarget.getIdentifier() > resolvedSource.getIdentifier();
// Example: if you want the changes to get from version 5 to 7, you
// need the changes contained in version 6
// and 7. The reason is that each version holds the changes which
// occurred from the predecessor to the
// version itself. Version 5 holds the changes to get from version 4
// to 5 and therefore is irrelevant.
// So the lower bound (source and target can be inverse too) has to
// be counted up by one.
if (resolvedSource.getIdentifier() < resolvedTarget.getIdentifier()) {
resolvedSource.setIdentifier(resolvedSource.getIdentifier() + 1);
} else {
resolvedTarget.setIdentifier(resolvedTarget.getIdentifier() + 1);
}
List<ChangePackage> result = new ArrayList<ChangePackage>();
for (Version version : getVersions(projectId, resolvedSource, resolvedTarget)) {
ChangePackage changes = version.getChanges();
changes.setLogMessage(EcoreUtil.copy(version.getLogMessage()));
result.add(changes);
}
// if source is after target in time
if (!updateForward) {
// reverse list and change packages
List<ChangePackage> resultReverse = new ArrayList<ChangePackage>();
for (ChangePackage changePackage : result) {
ChangePackage changePackageReverse = changePackage.reverse();
// copy again log message
// reverse() created a new change package without copying
// existent attributes
changePackageReverse.setLogMessage(EcoreUtil.copy(changePackage.getLogMessage()));
resultReverse.add(changePackageReverse);
}
Collections.reverse(resultReverse);
result = resultReverse;
}
return result;
}
}
/**
* Returns the specified version of a project.
*
* @param projectId
* project id
* @param versionSpec
* versionSpec
* @return the version
* @throws EmfStoreException
* if version couldn't be found
*/
protected Version getVersion(ProjectId projectId, PrimaryVersionSpec versionSpec) throws EmfStoreException {
EList<Version> versions = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId).getVersions();
if (versionSpec.getIdentifier() < 0 || versionSpec.getIdentifier() > versions.size() - 1) {
throw new InvalidVersionSpecException();
}
return versions.get(versionSpec.getIdentifier());
}
/**
* Returns a list of versions starting from source and ending with target.
* This method returns the version always in an ascanding order. So if you
* need it ordered differently you have to reverse the list.
*
* @param projectId
* project id
* @param source
* source
* @param target
* target
* @return list of versions
* @throws EmfStoreException
* if source or target are out of range or any other problem
* occurs
*/
protected List<Version> getVersions(ProjectId projectId, PrimaryVersionSpec source, PrimaryVersionSpec target)
throws EmfStoreException {
if (source.compareTo(target) < 1) {
EList<Version> versions = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId)
.getVersions();
if (source.getIdentifier() < 0 || source.getIdentifier() > versions.size() - 1
|| target.getIdentifier() < 0 || target.getIdentifier() > versions.size() - 1) {
throw new InvalidVersionSpecException();
}
List<Version> result = new ArrayList<Version>();
Iterator<Version> iter = versions.listIterator(source.getIdentifier());
int steps = target.getIdentifier() - source.getIdentifier();
while (iter.hasNext() && steps-- >= 0) {
result.add(iter.next());
}
return result;
} else {
return getVersions(projectId, target, source);
}
}
}