| /******************************************************************************* |
| * 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: |
| * Otto von Wesendonk - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.emf.emfstore.internal.server.core.subinterfaces; |
| |
| import java.io.IOException; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.UUID; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.emfstore.internal.common.APIUtil; |
| import org.eclipse.emf.emfstore.internal.common.ESCollections; |
| import org.eclipse.emf.emfstore.internal.common.model.Project; |
| import org.eclipse.emf.emfstore.internal.common.model.impl.ProjectImpl; |
| 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.EMFStoreController; |
| import org.eclipse.emf.emfstore.internal.server.ServerConfiguration; |
| import org.eclipse.emf.emfstore.internal.server.core.AbstractEmfstoreInterface; |
| import org.eclipse.emf.emfstore.internal.server.core.AbstractSubEmfstoreInterface; |
| import org.eclipse.emf.emfstore.internal.server.exceptions.BranchInfoMissingException; |
| import org.eclipse.emf.emfstore.internal.server.exceptions.FatalESException; |
| import org.eclipse.emf.emfstore.internal.server.exceptions.InvalidVersionSpecException; |
| import org.eclipse.emf.emfstore.internal.server.exceptions.StorageException; |
| import org.eclipse.emf.emfstore.internal.server.model.ProjectHistory; |
| import org.eclipse.emf.emfstore.internal.server.model.ProjectId; |
| import org.eclipse.emf.emfstore.internal.server.model.SessionId; |
| import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.ACUser; |
| import org.eclipse.emf.emfstore.internal.server.model.impl.api.ESUserImpl; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.AbstractChangePackage; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.AncestorVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.BranchInfo; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.BranchVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.ChangePackage; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.ChangePackageEnvelope; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.DateVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.FileBasedChangePackage; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.HeadVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.LogMessage; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.PagedUpdateVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.PrimaryVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.TagVersionSpec; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.Version; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.VersionSpec; |
| 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.internal.server.model.versioning.operations.AbstractOperation; |
| import org.eclipse.emf.emfstore.server.ESCloseableIterable; |
| import org.eclipse.emf.emfstore.server.auth.ESMethod; |
| import org.eclipse.emf.emfstore.server.auth.ESMethod.MethodId; |
| import org.eclipse.emf.emfstore.server.exceptions.ESException; |
| import org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException; |
| import org.eclipse.emf.emfstore.server.model.ESSessionId; |
| import org.eclipse.emf.emfstore.server.model.ESUser; |
| |
| import com.google.common.base.Optional; |
| |
| /** |
| * This subinterface implements all version related functionality. |
| * |
| * @author wesendon |
| */ |
| public class VersionSubInterfaceImpl extends AbstractSubEmfstoreInterface { |
| |
| /** |
| * Default constructor. |
| * |
| * @param parentInterface |
| * parent interface |
| * @throws FatalESException |
| * in case of failure |
| */ |
| public VersionSubInterfaceImpl(AbstractEmfstoreInterface parentInterface) throws FatalESException { |
| super(parentInterface); |
| } |
| |
| /** |
| * Resolves a versionSpec and delivers the corresponding primary |
| * versionSpec. |
| * |
| * @param projectId |
| * project id |
| * @param versionSpec |
| * versionSpec |
| * @return primary versionSpec |
| * |
| * @throws InvalidVersionSpecException |
| * if the project ID is invalid |
| * @throws ESException |
| * if versionSpec can't be resolved or other failure |
| */ |
| @ESMethod(MethodId.RESOLVEVERSIONSPEC) |
| public PrimaryVersionSpec resolveVersionSpec(ProjectId projectId, VersionSpec versionSpec) |
| throws InvalidVersionSpecException, ESException { |
| |
| sanityCheckObjects(projectId, versionSpec); |
| |
| synchronized (getMonitor()) { |
| final ProjectHistory projectHistory = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId); |
| |
| if (versionSpec instanceof PrimaryVersionSpec) { |
| return resolvePrimaryVersionSpec(projectHistory, (PrimaryVersionSpec) versionSpec); |
| } else if (versionSpec instanceof HeadVersionSpec) { |
| return resolveHeadVersionSpec(projectHistory, (HeadVersionSpec) versionSpec); |
| } else if (versionSpec instanceof DateVersionSpec) { |
| return resolveDateVersionSpec(projectHistory, (DateVersionSpec) versionSpec); |
| } else if (versionSpec instanceof TagVersionSpec) { |
| return resolveTagVersionSpec(projectHistory, (TagVersionSpec) versionSpec); |
| } else if (versionSpec instanceof BranchVersionSpec) { |
| return resolveBranchVersionSpec(projectHistory, (BranchVersionSpec) versionSpec); |
| } else if (versionSpec instanceof AncestorVersionSpec) { |
| return resolveAncestorVersionSpec(projectHistory, (AncestorVersionSpec) versionSpec); |
| } else if (versionSpec instanceof PagedUpdateVersionSpec) { |
| return resolvePagedUpdateVersionSpec(projectHistory, (PagedUpdateVersionSpec) versionSpec); |
| } |
| |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_UnknownVersionSpec); |
| } |
| } |
| |
| private PrimaryVersionSpec resolveAncestorVersionSpec(ProjectHistory projectHistory, |
| AncestorVersionSpec versionSpec) |
| throws InvalidVersionSpecException { |
| |
| Version currentSource = getVersion(projectHistory, versionSpec.getSource()); |
| Version currentTarget = getVersion(projectHistory, versionSpec.getTarget()); |
| |
| if (currentSource == null || currentTarget == null) { |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_Invalid_Source_Or_Target); |
| } |
| |
| // The goal is to find the common ancestor version of the source and |
| // target version from different branches. In |
| // order to find the ancestor the algorithm starts at the specified |
| // version and walks down the version tree in |
| // parallel for source and target until the current versions are equal |
| // and the ancestor is found. In Each step |
| // only one version (of target and source) is decremented. To find the |
| // global ancestor it is necessary that the |
| // version with the higher version number is decremented. |
| while (currentSource != null && currentTarget != null) { |
| if (currentSource == currentTarget) { |
| return currentSource.getPrimarySpec(); |
| } |
| |
| // Shortcut for most common merge usecase: If you have 2 parallel |
| // branches, only seperated by one level and merge several times from the one branch into the another. |
| // This case is also supported by #getVersions |
| if (currentSource.getMergedFromVersion().contains(currentTarget)) { |
| return currentTarget.getPrimarySpec(); |
| } |
| |
| if (currentSource.getPrimarySpec().getIdentifier() >= currentTarget.getPrimarySpec().getIdentifier()) { |
| currentSource = findNextVersion(currentSource); |
| } else { |
| currentTarget = findNextVersion(currentTarget); |
| } |
| |
| } |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_NoAncestorFound); |
| } |
| |
| private PrimaryVersionSpec resolvePrimaryVersionSpec(ProjectHistory projectHistory, PrimaryVersionSpec versionSpec) |
| throws InvalidVersionSpecException { |
| final int index = versionSpec.getIdentifier(); |
| final String branch = versionSpec.getBranch(); |
| final int versions = projectHistory.getVersions().size(); |
| if (0 > index || index >= versions || branch == null) { |
| throw new InvalidVersionSpecException(MessageFormat.format( |
| Messages.VersionSubInterfaceImpl_InvalidVersionRequested, |
| index)); |
| } |
| |
| if (branch.equals(VersionSpec.GLOBAL)) { |
| return projectHistory.getVersions().get(index).getPrimarySpec(); |
| } |
| |
| // Get biggest primary version of given branch which is equal or lower |
| // to the given versionSpec |
| for (int i = index; i >= 0; i--) { |
| final Version version = projectHistory.getVersions().get(i); |
| if (branch.equals(version.getPrimarySpec().getBranch())) { |
| return version.getPrimarySpec(); |
| } |
| |
| } |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_PrimaryVersionNotFound); |
| } |
| |
| private PrimaryVersionSpec resolveHeadVersionSpec(ProjectHistory projectHistory, HeadVersionSpec versionSpec) |
| throws InvalidVersionSpecException { |
| if (VersionSpec.GLOBAL.equals(versionSpec.getBranch())) { |
| return projectHistory.getVersions().get(projectHistory.getVersions().size() - 1).getPrimarySpec(); |
| } |
| final BranchInfo info = getBranchInfo(projectHistory, versionSpec); |
| if (info != null) { |
| return info.getHead(); |
| } |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_HeadVersionNotFound); |
| } |
| |
| private PrimaryVersionSpec resolveDateVersionSpec(ProjectHistory projectHistory, DateVersionSpec versionSpec) { |
| for (final Version version : projectHistory.getVersions()) { |
| final LogMessage logMessage = version.getLogMessage(); |
| if (logMessage == null || logMessage.getDate() == null) { |
| continue; |
| } |
| if (versionSpec.getDate().before(logMessage.getDate())) { |
| final Version previousVersion = version.getPreviousVersion(); |
| if (previousVersion == null) { |
| return VersioningFactory.eINSTANCE.createPrimaryVersionSpec(); |
| } |
| return previousVersion.getPrimarySpec(); |
| } |
| } |
| return projectHistory.getLastVersion().getPrimarySpec(); |
| } |
| |
| private PrimaryVersionSpec resolveTagVersionSpec(ProjectHistory projectHistory, TagVersionSpec versionSpec) |
| throws InvalidVersionSpecException { |
| for (final Version version : projectHistory.getVersions()) { |
| for (final TagVersionSpec tag : version.getTagSpecs()) { |
| if (versionSpec.equals(tag)) { |
| return ModelUtil.clone(version.getPrimarySpec()); |
| } |
| } |
| } |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_TagVersionNotFound); |
| } |
| |
| private PrimaryVersionSpec resolveBranchVersionSpec(ProjectHistory projectHistory, BranchVersionSpec versionSpec) |
| throws BranchInfoMissingException { |
| final BranchInfo branchInfo = getBranchInfo(projectHistory, versionSpec); |
| if (branchInfo == null) { |
| throw new BranchInfoMissingException(Messages.VersionSubInterfaceImpl_NoBranchInfo); |
| } |
| return branchInfo.getHead(); |
| } |
| |
| /** |
| * Given a {@link ChangePackageEnvelope} which contains a change package fragment, |
| * stores one or more fragments by attaching them to a session specific adapter. |
| * |
| * @param sessionId |
| * the {@link SessionId} belonging to the calling user |
| * @param projectId |
| * the {@link ProjectId} |
| * @param envelope |
| * the {@link ChangePackageEnvelope} containing the fragment |
| * @return an ID identifying the stored fragment(s) |
| * @throws ESException in case the fragment couldn't be stored |
| */ |
| @ESMethod(MethodId.UPLOADCHANGEPACKAGEFRAGMENT) |
| public String uploadChangePackageFragment(SessionId sessionId, |
| ProjectId projectId, |
| ChangePackageEnvelope envelope) throws ESException { |
| |
| final String proxyId = generateProxyId(projectId.getId()); |
| |
| final ESSessionId resolvedSession = getAccessControl().getSessions().resolveSessionById(sessionId.getId()); |
| |
| if (resolvedSession == null) { |
| throw new ESException( |
| MessageFormat.format( |
| Messages.VersionSubInterfaceImpl_0, sessionId.getId())); |
| } |
| |
| final SessionId session = APIUtil.toInternal(SessionId.class, resolvedSession); |
| final Optional<ChangePackageFragmentUploadAdapter> maybeAdapter = ESCollections.find(session.eAdapters(), |
| ChangePackageFragmentUploadAdapter.class); |
| ChangePackageFragmentUploadAdapter adapter; |
| |
| if (!maybeAdapter.isPresent()) { |
| adapter = new ChangePackageFragmentUploadAdapter(); |
| session.eAdapters().add(adapter); |
| } else { |
| adapter = maybeAdapter.get(); |
| } |
| |
| adapter.addFragment(proxyId, envelope.getFragment()); |
| if (envelope.isLast()) { |
| adapter.markAsComplete(proxyId); |
| } |
| |
| return proxyId; |
| } |
| |
| /** |
| * Fetches a single change package fragment. |
| * |
| * @param sessionId |
| * the {@link SessionId} representing the requesting user |
| * @param proxyId |
| * the ID that identifies the list of stored fragments |
| * @param fragmentIndex |
| * allows to request different change package fragments |
| * @return a {@link ChangePackageEnvelope} containing the change package fragment |
| * @throws ESException in case the mandatory session adapter is missing |
| */ |
| @ESMethod(MethodId.DOWNLOADCHANGEPACKAGEFRAGMENT) |
| public ChangePackageEnvelope downloadChangePackageFragment(SessionId sessionId, String proxyId, int fragmentIndex) |
| throws ESException { |
| |
| final ESSessionId resolvedSession = getAccessControl().getSessions().resolveSessionById(sessionId.getId()); |
| final SessionId session = APIUtil.toInternal(SessionId.class, resolvedSession); |
| final Optional<ChangePackageFragmentProviderAdapter> maybeAdapter = ESCollections.find(session.eAdapters(), |
| ChangePackageFragmentProviderAdapter.class); |
| |
| if (!maybeAdapter.isPresent()) { |
| throw new ESException(Messages.VersionSubInterfaceImpl_ChangePackageFragmentProviderAdapterMissing |
| + sessionId); |
| } |
| |
| final ChangePackageFragmentProviderAdapter adapter = maybeAdapter.get(); |
| final ChangePackageEnvelope envelope = VersioningFactory.eINSTANCE.createChangePackageEnvelope(); |
| final List<AbstractOperation> fragment = adapter.getFragment(proxyId, fragmentIndex); |
| |
| envelope.getFragment().addAll(fragment); |
| envelope.setFragmentCount(adapter.getFragmentSize(proxyId)); |
| envelope.setFragmentIndex(fragmentIndex); |
| |
| if (envelope.isLast()) { |
| adapter.markAsConsumed(proxyId); |
| } |
| |
| return envelope; |
| } |
| |
| private String generateProxyId(String projectId) { |
| return UUID.nameUUIDFromBytes(projectId.getBytes()).toString(); |
| } |
| |
| /** |
| * Create a new version. |
| * |
| * @param sessionId |
| * the ID of the session being used to create a new version |
| * @param projectId |
| * the ID of the project for which a new version is created |
| * @param baseVersionSpec |
| * the base version |
| * @param changePackage |
| * the change package containing all changes that make up the new version |
| * @param targetBranch |
| * the target branch for which to create the new version |
| * @param sourceVersion |
| * the source version |
| * @param logMessage |
| * a log message |
| * @return the new version |
| * @throws ESException in case of failure |
| */ |
| @ESMethod(MethodId.CREATEVERSION) |
| public PrimaryVersionSpec createVersion(SessionId sessionId, ProjectId projectId, |
| PrimaryVersionSpec baseVersionSpec, AbstractChangePackage changePackage, BranchVersionSpec targetBranch, |
| PrimaryVersionSpec sourceVersion, LogMessage logMessage) throws ESException { |
| |
| getAccessControl().getSessions().isValid(sessionId.toAPI()); |
| final ESUser rawUser = getAccessControl().getSessions().getRawUser(sessionId.toAPI()); |
| final ACUser tmpUser = (ACUser) ESUserImpl.class.cast(rawUser).toInternalAPI(); |
| final ESUser copyAndResolveUser = getAccessControl().getOrgUnitResolverServive().copyAndResolveUser( |
| tmpUser.toAPI()); |
| final ACUser user = (ACUser) ESUserImpl.class.cast(copyAndResolveUser).toInternalAPI(); |
| sanityCheckObjects(sessionId, projectId, baseVersionSpec, changePackage, logMessage); |
| |
| if (FileBasedChangePackage.class.isInstance(changePackage) |
| && !ServerConfiguration.useFileBasedChangePackageOnServer()) { |
| // File-based change package should never arrive here in production mode |
| throw new ESException(Messages.VersionSubInterfaceImpl_FileBasedChangePackageNotAllowed); |
| } else if (ChangePackage.class.isInstance(changePackage) |
| && ServerConfiguration.useFileBasedChangePackageOnServer()) { |
| // Regular change package should never arrive here in production mode |
| throw new ESException(Messages.VersionSubInterfaceImpl_FileBasedChangePackageExpected); |
| } |
| |
| return internalCreateVersion(projectId, baseVersionSpec, changePackage, targetBranch, sourceVersion, |
| logMessage, user); |
| } |
| |
| private PrimaryVersionSpec internalCreateVersion(ProjectId projectId, PrimaryVersionSpec baseVersionSpec, |
| AbstractChangePackage changePackage, BranchVersionSpec targetBranch, PrimaryVersionSpec sourceVersion, |
| LogMessage logMessage, final ACUser user) throws ESException { |
| synchronized (getMonitor()) { |
| |
| final long currentTimeMillis = System.currentTimeMillis(); |
| final ProjectHistory projectHistory = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId); |
| |
| // Find branch |
| final BranchInfo baseBranch = getBranchInfo(projectHistory, baseVersionSpec); |
| final Version baseVersion = getVersion(projectHistory, baseVersionSpec); |
| |
| if (baseVersion == null || baseBranch == null) { |
| throw new InvalidVersionSpecException( |
| Messages.VersionSubInterfaceImpl_InvalidBranchOrVersion); |
| } |
| |
| // defined here fore scoping reasons |
| Version newVersion = null; |
| BranchInfo newBranch = null; |
| |
| // copy project and apply changes |
| final Project newProjectState = ((ProjectImpl) getSubInterface(ProjectSubInterfaceImpl.class).getProject( |
| baseVersion)).copy(); |
| changePackage.apply(newProjectState); |
| |
| // regular commit |
| if (isRegularCommit(targetBranch, baseVersion)) { |
| |
| newVersion = performRegularCommit(baseVersionSpec, logMessage, user, projectHistory, baseBranch, |
| baseVersion, newProjectState); |
| |
| // case for new branch creation |
| } else if (isNewBranchCommit(targetBranch, projectHistory)) { |
| checkNewBranchCommitPreRequisites(targetBranch.getBranch()); |
| // when branch does NOT exist, create new branch |
| newVersion = createVersion(projectHistory, newProjectState, logMessage, user, baseVersion); |
| newBranch = createNewBranch(projectHistory, baseVersion.getPrimarySpec(), newVersion.getPrimarySpec(), |
| targetBranch); |
| newVersion.setAncestorVersion(baseVersion); |
| |
| } else { |
| // This point only can be reached with invalid input |
| throw new IllegalStateException(Messages.VersionSubInterfaceImpl_TargetBranchCombination_Invalid); |
| } |
| |
| if (sourceVersion != null) { |
| newVersion.getMergedFromVersion().add(getVersion(projectHistory, sourceVersion)); |
| } |
| |
| // try to save |
| try { |
| try { |
| trySave(projectId, changePackage, projectHistory, newVersion, newProjectState); |
| } catch (final FatalESException e) { |
| // try to roll back. removing version is necessary in all cases |
| rollback(projectHistory, baseBranch, baseVersion, newVersion, newBranch, e); |
| } |
| |
| // if ancestor isn't null, a new branch was created. In this |
| // case we want to keep the old base project |
| // state |
| if (newVersion.getAncestorVersion() == null && baseVersion.getProjectState() != null) { |
| // delete projectstate from last revision depending on |
| // persistence policy |
| deleteOldProjectSpaceAccordingToOptions(projectId, baseVersion); |
| } |
| |
| save(baseVersion); |
| save(projectHistory); |
| |
| } catch (final FatalESException e) { |
| // roll back failed |
| EMFStoreController.getInstance().shutdown(e); |
| throw new ESException(Messages.VersionSubInterfaceImpl_ShuttingServerDown); |
| } |
| |
| ModelUtil.logInfo( |
| Messages.VersionSubInterfaceImpl_TotalTimeForCommit + (System.currentTimeMillis() - currentTimeMillis)); |
| return newVersion.getPrimarySpec(); |
| } |
| } |
| |
| /** |
| * @param targetBranch |
| * @throws InvalidVersionSpecException |
| */ |
| private void checkNewBranchCommitPreRequisites(String targetBranchName) throws InvalidVersionSpecException { |
| if (targetBranchName.equals(StringUtils.EMPTY)) { |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_EmptyBranch_Not_Allowed); |
| } else if (targetBranchName.equals(VersionSpec.GLOBAL)) { |
| throw new InvalidVersionSpecException( |
| Messages.VersionSubInterfaceImpl_BranchName_Reserved_1 |
| + VersionSpec.GLOBAL + Messages.VersionSubInterfaceImpl_BranchName_Reserved_2); |
| } |
| } |
| |
| private Version performRegularCommit(PrimaryVersionSpec baseVersionSpec, LogMessage logMessage, final ACUser user, |
| final ProjectHistory projectHistory, final BranchInfo baseBranch, final Version baseVersion, |
| final Project newProjectState) throws ESUpdateRequiredException, ESException { |
| Version newVersion; |
| // If branch is null or branch equals base branch, create new |
| // version for specific branch |
| if (!baseVersionSpec.equals(isHeadOfBranch(projectHistory, baseVersion.getPrimarySpec()))) { |
| throw new ESUpdateRequiredException(); |
| } |
| newVersion = createVersion(projectHistory, newProjectState, logMessage, user, baseVersion); |
| newVersion.setPreviousVersion(baseVersion); |
| baseBranch.setHead(ModelUtil.clone(newVersion.getPrimarySpec())); |
| return newVersion; |
| } |
| |
| /** |
| * @param targetBranch |
| * @param projectHistory |
| * @return |
| */ |
| private boolean isNewBranchCommit(BranchVersionSpec targetBranch, final ProjectHistory projectHistory) { |
| return getBranchInfo(projectHistory, targetBranch) == null; |
| } |
| |
| /** |
| * @param targetBranch |
| * @param baseVersion |
| * @return |
| */ |
| private boolean isRegularCommit(BranchVersionSpec targetBranch, final Version baseVersion) { |
| return targetBranch == null || baseVersion.getPrimarySpec().getBranch().equals(targetBranch.getBranch()); |
| } |
| |
| private void rollback(final ProjectHistory projectHistory, final BranchInfo baseBranch, |
| final Version baseVersion, Version newVersion, BranchInfo newBranch, final FatalESException e) |
| throws StorageException { |
| projectHistory.getVersions().remove(newVersion); |
| |
| if (newBranch == null) { |
| // normal commit |
| baseVersion.setNextVersion(null); |
| baseBranch.setHead(ModelUtil.clone(baseVersion.getPrimarySpec())); |
| } else { |
| // branch commit |
| baseVersion.getBranchedVersions().remove(newVersion); |
| projectHistory.getBranches().remove(newBranch); |
| } |
| // TODO: delete obsolete project, change package and version files |
| throw new StorageException(StorageException.NOSAVE, e); |
| } |
| |
| /** |
| * @param projectId |
| * @param changePackage |
| * @param projectHistory |
| * @param newVersion |
| * @param newProjectState |
| * @throws FatalESException |
| */ |
| private void trySave(ProjectId projectId, AbstractChangePackage changePackage, final ProjectHistory projectHistory, |
| Version newVersion, final Project newProjectState) throws FatalESException { |
| getResourceHelper().createResourceForProject(newProjectState, |
| newVersion.getPrimarySpec(), projectHistory.getProjectId()); |
| getResourceHelper().createResourceForChangePackage(changePackage, newVersion.getPrimarySpec(), |
| projectId); |
| if (FileBasedChangePackage.class.isInstance(changePackage)) { |
| try { |
| /* move the temporary file to the project folder */ |
| final URI uri = changePackage.eResource().getURI(); |
| final URI normalizedUri = changePackage.eResource().getResourceSet().getURIConverter().normalize(uri); |
| final String filePath = normalizedUri.toFileString() + ".1"; //$NON-NLS-1$ |
| FileBasedChangePackage.class.cast(changePackage).move(filePath); |
| ModelUtil.saveResource(changePackage.eResource(), ModelUtil.getResourceLogger()); |
| } catch (final IOException ex) { |
| throw new FatalESException(StorageException.NOSAVE, ex); |
| } |
| } |
| |
| getResourceHelper().createResourceForVersion(newVersion, projectHistory.getProjectId()); |
| |
| newVersion.setProjectStateResource(newProjectState.eResource()); |
| newVersion.setChangeResource(changePackage.eResource()); |
| } |
| |
| private BranchInfo createNewBranch(ProjectHistory projectHistory, PrimaryVersionSpec baseSpec, |
| PrimaryVersionSpec primarySpec, BranchVersionSpec branch) { |
| primarySpec.setBranch(branch.getBranch()); |
| |
| final BranchInfo branchInfo = VersioningFactory.eINSTANCE.createBranchInfo(); |
| branchInfo.setName(branch.getBranch()); |
| branchInfo.setSource(ModelUtil.clone(baseSpec)); |
| branchInfo.setHead(ModelUtil.clone(primarySpec)); |
| |
| projectHistory.getBranches().add(branchInfo); |
| |
| return branchInfo; |
| } |
| |
| private Version createVersion(ProjectHistory projectHistory, Project projectState, LogMessage logMessage, |
| ACUser user, Version previousVersion) throws ESException { |
| final Version newVersion = VersioningFactory.eINSTANCE.createVersion(); |
| |
| long computedChecksum = ModelUtil.NO_CHECKSUM; |
| |
| try { |
| if (ServerConfiguration.isComputeChecksumOnCommitActive()) { |
| computedChecksum = ModelUtil.computeChecksum(projectState); |
| } |
| } catch (final SerializationException exception) { |
| // TODO: clarify what to do in case checksum computation fails + provide ext. point |
| throw new ESException(MessageFormat.format( |
| Messages.VersionSubInterfaceImpl_ChecksumComputationFailed, |
| projectHistory.getProjectName()), exception); |
| } |
| |
| // newVersion.setChanges(changePackage); |
| |
| logMessage.setDate(new Date()); |
| logMessage.setAuthor(user.getName()); |
| newVersion.setLogMessage(logMessage); |
| |
| // latest version == getVersion.size() (version start with index 0 as |
| // the list), branch from previous is used. |
| newVersion.setPrimarySpec(Versions.createPRIMARY(previousVersion.getPrimarySpec(), projectHistory.getVersions() |
| .size())); |
| newVersion.getPrimarySpec().setProjectStateChecksum(computedChecksum); |
| newVersion.setNextVersion(null); |
| |
| projectHistory.getVersions().add(newVersion); |
| return newVersion; |
| } |
| |
| private Version getVersion(ProjectHistory projectHistory, PrimaryVersionSpec baseVersionSpec) { |
| if (0 > baseVersionSpec.getIdentifier() |
| || baseVersionSpec.getIdentifier() > projectHistory.getVersions().size() - 1) { |
| return null; |
| } |
| final Version version = projectHistory.getVersions().get(baseVersionSpec.getIdentifier()); |
| if (version == null || !version.getPrimarySpec().equals(baseVersionSpec)) { |
| return null; |
| } |
| return version; |
| } |
| |
| private PrimaryVersionSpec isHeadOfBranch(ProjectHistory projectHistory, PrimaryVersionSpec versionSpec) { |
| final BranchInfo branchInfo = getBranchInfo(projectHistory, versionSpec); |
| if (branchInfo != null && branchInfo.getHead().equals(versionSpec)) { |
| return branchInfo.getHead(); |
| } |
| return null; |
| } |
| |
| private BranchInfo getBranchInfo(ProjectHistory projectHistory, VersionSpec versionSpec) { |
| for (final BranchInfo branchInfo : projectHistory.getBranches()) { |
| if (branchInfo.getName().equals(versionSpec.getBranch())) { |
| return branchInfo; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns all branches for the project with the given ID. |
| * |
| * @param projectId |
| * the ID of a project |
| * @return a list containing information about each branch |
| * @throws ESException in case of failure |
| */ |
| @ESMethod(MethodId.GETBRANCHES) |
| public List<BranchInfo> getBranches(ProjectId projectId) throws ESException { |
| synchronized (getMonitor()) { |
| final ProjectHistory projectHistory = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId); |
| final ArrayList<BranchInfo> result = new ArrayList<BranchInfo>(); |
| for (final BranchInfo branch : projectHistory.getBranches()) { |
| result.add(ModelUtil.clone(branch)); |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * Deletes projectstate from last revision depending on persistence policy. |
| * |
| * @param projectId |
| * project id |
| * @param previousHeadVersion |
| * last head version |
| */ |
| private void deleteOldProjectSpaceAccordingToOptions(ProjectId projectId, Version previousHeadVersion) { |
| final String property = ServerConfiguration.getProperties().getProperty( |
| ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE, |
| ServerConfiguration.PROJECTSPACE_VERSION_PERSISTENCE_DEFAULT); |
| |
| if (property.equals(ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS)) { |
| |
| final int x = getResourceHelper().getXFromPolicy( |
| ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS_X, |
| ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS_X_DEFAULT, false); |
| |
| // always save projecstate of first version |
| final int lastVersion = previousHeadVersion.getPrimarySpec().getIdentifier(); |
| if (lastVersion != 0 && lastVersion % x != 0) { |
| getResourceHelper().deleteProjectState(previousHeadVersion, projectId); |
| } |
| |
| } else { |
| getResourceHelper().deleteProjectState(previousHeadVersion, projectId); |
| } |
| } |
| |
| /** |
| * Returns all changes within the specified version range for a given project. |
| * |
| * @param projectId |
| * the ID of a project |
| * @param source |
| * the source version |
| * @param target |
| * the target version (inclusive) |
| * @return a list of change packages containing all the changes for the specified version range |
| * |
| * @throws InvalidVersionSpecException |
| * if an invalid version has been specified |
| * @throws ESException |
| * in case of failure |
| */ |
| @ESMethod(MethodId.GETCHANGES) |
| public List<AbstractChangePackage> getChanges(ProjectId projectId, VersionSpec source, VersionSpec target) |
| throws InvalidVersionSpecException, ESException { |
| |
| sanityCheckObjects(projectId, source, target); |
| final PrimaryVersionSpec resolvedSource = resolveVersionSpec(projectId, source); |
| final PrimaryVersionSpec resolvedTarget = resolveVersionSpec(projectId, target); |
| // if target and source are equal return empty list |
| if (resolvedSource.getIdentifier() == resolvedTarget.getIdentifier()) { |
| return new ArrayList<AbstractChangePackage>(); |
| } |
| |
| synchronized (getMonitor()) { |
| |
| final 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. |
| // For that reason the first version is removed, since getVersions |
| // always sorts ascending order. |
| final List<Version> versions = getVersions(projectId, resolvedSource, resolvedTarget); |
| if (versions.size() > 1) { |
| versions.remove(0); |
| } |
| |
| List<AbstractChangePackage> result = new ArrayList<AbstractChangePackage>(); |
| for (final Version version : versions) { |
| final AbstractChangePackage changes = version.getChanges(); |
| if (changes != null) { |
| changes.setLogMessage(ModelUtil.clone(version.getLogMessage())); |
| result.add(changes); |
| } |
| } |
| |
| // if source is after target in time |
| if (!updateForward) { |
| // reverse list and change packages |
| final List<AbstractChangePackage> resultReverse = new ArrayList<AbstractChangePackage>(); |
| for (final AbstractChangePackage changePackage : result) { |
| |
| final ChangePackage changePackageReverse = VersioningFactory.eINSTANCE.createChangePackage(); |
| final ESCloseableIterable<AbstractOperation> reversedOperations = changePackage |
| .reversedOperations(); |
| final ArrayList<AbstractOperation> copiedReversedOperations = new ArrayList<AbstractOperation>(); |
| try { |
| for (final AbstractOperation op : reversedOperations.iterable()) { |
| copiedReversedOperations.add(op.reverse()); |
| } |
| } finally { |
| reversedOperations.close(); |
| } |
| |
| for (final AbstractOperation reversedOperation : copiedReversedOperations) { |
| changePackageReverse.add(reversedOperation); |
| } |
| |
| // copy again log message |
| // reverse() created a new change package without copying |
| // existent attributes |
| changePackageReverse.setLogMessage(ModelUtil.clone( |
| 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 ESException |
| * if version couldn't be found |
| */ |
| protected Version getVersion(ProjectId projectId, PrimaryVersionSpec versionSpec) throws ESException { |
| final ProjectHistory project = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId); |
| return getVersion(project, versionSpec); |
| } |
| |
| /** |
| * 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 ESException |
| * if source or target are out of range or any other problem |
| * occurs |
| */ |
| protected List<Version> getVersions(ProjectId projectId, PrimaryVersionSpec source, PrimaryVersionSpec target) |
| throws ESException { |
| if (source.compareTo(target) < 1) { |
| final ProjectHistory projectHistory = getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId); |
| |
| final Version sourceVersion = getVersion(projectHistory, source); |
| final Version targetVersion = getVersion(projectHistory, target); |
| |
| if (sourceVersion == null || targetVersion == null) { |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_NoSourceNorTarget); |
| } |
| final List<Version> result = new ArrayList<Version>(); |
| |
| // since the introduction of branches the versions are collected |
| // in different order. |
| Version currentVersion = targetVersion; |
| while (currentVersion != null) { |
| result.add(currentVersion); |
| if (currentVersion.equals(sourceVersion)) { |
| break; |
| } |
| if (currentVersion.getPrimarySpec().compareTo(sourceVersion.getPrimarySpec()) < 0) { |
| // walked too far, invalid path. |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_InvalidPath); |
| } |
| // Shortcut for most common merge usecase: If you have 2 |
| // parallel branches and merge several times |
| // from the one branch into the another. |
| if (currentVersion.getMergedFromVersion().contains(sourceVersion)) { |
| // add sourceVersion because #getChanges always removes |
| // the first version |
| result.add(sourceVersion); |
| break; |
| } |
| |
| currentVersion = findNextVersion(currentVersion); |
| } |
| // versions are collected in descending order, so the result has to be reversed. |
| Collections.reverse(result); |
| return result; |
| } |
| |
| return getVersions(projectId, target, source); |
| } |
| |
| /** |
| * Helper method which retrieves the next version in the history tree. This |
| * method must be used in reversed order. With the introduction of branches, the versions are organized in a tree |
| * structure. Therefore, next versions are always searched for walking up the tree. |
| * |
| * @param currentVersion |
| * current version |
| * @return version |
| * @throws InvalidVersionSpecException |
| * if the path can't be followed further |
| */ |
| public static Version findNextVersion(Version currentVersion) throws InvalidVersionSpecException { |
| // find next version |
| if (currentVersion.getPreviousVersion() != null) { |
| currentVersion = currentVersion.getPreviousVersion(); |
| } else if (currentVersion.getAncestorVersion() != null) { |
| currentVersion = currentVersion.getAncestorVersion(); |
| } else { |
| throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_NextVersionInvalid); |
| } |
| return currentVersion; |
| } |
| |
| private PrimaryVersionSpec resolvePagedUpdateVersionSpec(ProjectHistory projectHistory, |
| PagedUpdateVersionSpec baseVersion) { |
| |
| int changes = 0; |
| PrimaryVersionSpec resolvedSpec = baseVersion.getBaseVersionSpec(); |
| int maxChanges = baseVersion.getMaxChanges(); |
| |
| int i = resolvedSpec.getIdentifier(); |
| |
| AbstractChangePackage cp = projectHistory.getVersions().get(i).getChanges(); |
| |
| if (i == projectHistory.getVersions().size() - 1) { |
| return projectHistory.getVersions().get(i).getPrimarySpec(); |
| } |
| |
| do { |
| cp = projectHistory.getVersions().get(++i).getChanges(); |
| } while (cp == null && i < projectHistory.getVersions().size()); |
| |
| // pull at least one change package |
| if (cp.leafSize() > maxChanges) { |
| maxChanges = cp.leafSize(); |
| } |
| |
| while (changes < maxChanges && i < projectHistory.getVersions().size()) { |
| resolvedSpec = projectHistory.getVersions().get(i).getPrimarySpec(); |
| final Version version = projectHistory.getVersions().get(i); |
| final AbstractChangePackage changePackage = version.getChanges(); |
| |
| if (changePackage != null) { |
| final int size = changePackage.leafSize(); |
| if (changes + size >= maxChanges) { |
| resolvedSpec = projectHistory.getVersions().get(i).getPrimarySpec(); |
| break; |
| } |
| changes += size; |
| } |
| i += 1; |
| } |
| |
| return resolvedSpec; |
| } |
| } |