/* * Copyright (c) 2015, 2016 Eike Stepper (Berlin, Germany) and others. * 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: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.internal.explorer.checkouts; import org.eclipse.emf.cdo.common.branch.CDOBranch; import org.eclipse.emf.cdo.common.branch.CDOBranchManager; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.cdo.explorer.CDOExplorerUtil; import org.eclipse.emf.cdo.explorer.checkouts.CDOCheckout; import org.eclipse.emf.cdo.explorer.repositories.CDORepository; import org.eclipse.emf.cdo.internal.explorer.AbstractElement; import org.eclipse.emf.cdo.internal.explorer.AbstractManager; import org.eclipse.emf.cdo.internal.explorer.bundle.OM; import org.eclipse.emf.cdo.internal.explorer.repositories.CDORepositoryImpl; import org.eclipse.emf.cdo.session.CDOSession; import org.eclipse.emf.cdo.transaction.CDOTransaction; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.emf.cdo.util.ReadOnlyException; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.emf.cdo.view.CDOViewLocksChangedEvent; import org.eclipse.emf.cdo.view.CDOViewTargetChangedEvent; import org.eclipse.net4j.util.ObjectUtil; import org.eclipse.net4j.util.StringUtil; import org.eclipse.net4j.util.container.IManagedContainer; import org.eclipse.net4j.util.container.IPluginContainer; import org.eclipse.net4j.util.event.IEvent; import org.eclipse.net4j.util.event.IListener; import org.eclipse.net4j.util.lifecycle.ILifecycle; import org.eclipse.net4j.util.lifecycle.ILifecycleEvent; import org.eclipse.net4j.util.lifecycle.ILifecycleEvent.Kind; import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.spi.cdo.InternalCDOObject; import org.eclipse.emf.spi.cdo.InternalCDOView; import org.eclipse.core.runtime.Path; import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.WeakHashMap; /** * @author Eike Stepper */ public abstract class CDOCheckoutImpl extends AbstractElement implements CDOCheckout { public static final String PROP_ROOT_ID = "rootID"; public static final String PROP_READ_ONLY = "readOnly"; public static final String PROP_TIME_STAMP = "timeStamp"; public static final String PROP_BRANCH_ID = "branchID"; public static final String PROP_BRANCH_PATH = "branchPath"; public static final String PROP_BRANCH_POINTS = "branchPoints"; public static final String PROP_REPOSITORY = "repository"; public static final String EDITOR_PROPERTIES = "editor.properties"; public static final String CHECKOUT_KEY = CDOCheckout.class.getName(); private static final CDOBranchPoint[] NO_BRANCH_POINTS = {}; private static final int BRANCH_POINTS_MAX = 10; private static final String BRANCH_POINT_SEPARATOR = ","; private static final String BRANCH_AND_POINT_SEPARATOR = "_"; private final Set<CDOView> views = new HashSet<CDOView>(); private final Map<CDOID, String> editorIDs = new WeakHashMap<CDOID, String>(); private CDORepository repository; private int branchID; private String branchPath; private String branchPoints; private long timeStamp; private boolean readOnly; private CDOID rootID; private State state = State.Closed; private CDOView view; private EObject rootObject; private URI uri; public CDOCheckoutImpl() { } public CDOCheckoutImpl(boolean readOnly) { this.readOnly = readOnly; } @Override public final CDOCheckoutManagerImpl getManager() { return OM.getCheckoutManager(); } public final CDORepository getRepository() { return repository; } public final int getBranchID() { return branchID; } public final void setBranchID(int branchID) { setBranchPoint(branchID, timeStamp); } public String getBranchPath() { return branchPath; } public final void setBranchPath(String branchPath) { if (!ObjectUtil.equals(this.branchPath, branchPath)) { this.branchPath = branchPath; save(); } } public final CDOBranchPoint getBranchPoint() { return view; } public final void setBranchPoint(CDOBranchPoint branchPoint) { int branchID = branchPoint.getBranch().getID(); long timeStamp = branchPoint.getTimeStamp(); setBranchPoint(branchID, timeStamp); } public final void setBranchPoint(int branchID, long timeStamp) { if (this.branchID != branchID || this.timeStamp != timeStamp) { addBranchPoint(this.branchID, this.timeStamp); this.branchID = branchID; this.timeStamp = timeStamp; if (isOpen()) { branchPath = doSetBranchPoint(branchID, timeStamp); for (CDOView view : getViews()) { if (view != this.view) { view.setBranchPoint(this.view); } } } else { branchPath = null; } save(); } } public final CDOBranchPoint getBranchPoint(CDOCheckout fromCheckout) { if (repository == fromCheckout.getRepository() && repository.isConnected()) { CDOBranchManager branchManager = repository.getSession().getBranchManager(); CDOBranch branch = branchManager.getBranch(fromCheckout.getBranchID()); return branch.getPoint(fromCheckout.getTimeStamp()); } return null; } protected String doSetBranchPoint(int branchID, long timeStamp) { CDOBranch branch = view.getSession().getBranchManager().getBranch(branchID); view.setBranchPoint(branch.getPoint(timeStamp)); return branch.getPathName(); } public CDOBranchPoint[] getBranchPoints() { if (branchPoints != null && isOpen()) { List<CDOBranchPoint> result = new ArrayList<CDOBranchPoint>(); CDOBranchManager branchManager = view.getSession().getBranchManager(); for (String token : branchPoints.split(BRANCH_POINT_SEPARATOR)) { String[] branchAndPoint = token.split(BRANCH_AND_POINT_SEPARATOR); int branchID = Integer.parseInt(branchAndPoint[0]); CDOBranch branch = branchManager.getBranch(branchID); if (branch != null) { long timeStamp; if (branchAndPoint.length >= 2) { timeStamp = Long.parseLong(branchAndPoint[1]); } else { timeStamp = CDOBranchPoint.UNSPECIFIED_DATE; } result.add(branch.getPoint(timeStamp)); } } return result.toArray(new CDOBranchPoint[result.size()]); } return NO_BRANCH_POINTS; } public boolean addBranchPoint(CDOBranchPoint branchPoint) { int branchID = branchPoint.getBranch().getID(); long timeStamp = branchPoint.getTimeStamp(); boolean changed = addBranchPoint(branchID, timeStamp); if (changed) { save(); } return changed; } private boolean addBranchPoint(int branchID, long timeStamp) { String oldBranchPoints = branchPoints; String newToken = Integer.toString(branchID); if (timeStamp != CDOBranchPoint.UNSPECIFIED_DATE) { newToken += BRANCH_AND_POINT_SEPARATOR + timeStamp; } if (branchPoints != null) { StringBuilder builder = new StringBuilder(newToken); int count = 1; for (String token : branchPoints.split(BRANCH_POINT_SEPARATOR)) { if (count++ == BRANCH_POINTS_MAX) { break; } if (!token.equals(newToken)) { builder.append(BRANCH_POINT_SEPARATOR); builder.append(token); } } branchPoints = builder.toString(); } else { branchPoints = newToken; } if (!ObjectUtil.equals(branchPoints, oldBranchPoints)) { return true; } return false; } public final long getTimeStamp() { return timeStamp; } public final void setTimeStamp(long timeStamp) { setBranchPoint(branchID, timeStamp); } public final boolean isReadOnly() { return readOnly; } public void setReadOnly(boolean readOnly) { if (state != State.Closed) { throw new IllegalStateException("Checkout is not closed: " + this); } if (this.readOnly != readOnly) { this.readOnly = readOnly; save(); } } public final CDOID getRootID() { return rootID; } public final void setRootID(CDOID rootID) { this.rootID = rootID; } public CDOCheckout duplicate() { Properties properties = new Properties(); collectDuplicationProperties(properties); CDOCheckout copy = CDOExplorerUtil.getCheckoutManager().addCheckout(properties); if (isOpen()) { copy.open(); } return copy; } protected void collectDuplicationProperties(Properties properties) { properties.setProperty(PROP_TYPE, getType()); properties.setProperty(PROP_LABEL, getManager().getUniqueLabel(getLabel())); properties.setProperty(PROP_REPOSITORY, getRepository().getID()); properties.setProperty(PROP_BRANCH_ID, Integer.toString(getBranchID())); properties.setProperty(PROP_TIME_STAMP, Long.toString(getTimeStamp())); properties.setProperty(PROP_READ_ONLY, Boolean.toString(isReadOnly())); properties.setProperty(PROP_ROOT_ID, getCDOIDString(getRootID())); } public final State getState() { return state; } public final boolean isOpen() { return view != null; } public final void open() { CDOCheckoutManagerImpl manager = getManager(); State oldState = null; State newState = null; try { synchronized (this) { oldState = state; if (!isOpen()) { try { state = State.Opening; if (manager != null) { manager.fireCheckoutOpenEvent(this, null, oldState, state); } oldState = state; prepareOpen(); CDOSession session = ((CDORepositoryImpl)repository).openCheckout(this); view = openView(session); view.addListener(new LifecycleEventAdapter() { @Override protected void onDeactivated(ILifecycle lifecycle) { removeView(view); if (state != State.Closing) { close(); } } }); configureView(view); rootObject = loadRootObject(); rootObject.eAdapters().add(this); state = State.Open; newState = state; } catch (RuntimeException ex) { view = null; rootObject = null; state = State.Closed; newState = state; throw ex; } catch (Error ex) { view = null; rootObject = null; state = State.Closed; newState = state; throw ex; } } } } finally { if (manager != null && oldState != null && newState != null && newState != oldState) { manager.fireCheckoutOpenEvent(this, view, oldState, newState); } } } protected void prepareOpen() { // Do nothing. } public final void close() { boolean closed = false; CDOView oldView = null; synchronized (this) { if (isOpen()) { try { state = State.Closing; oldView = view; try { rootObject.eAdapters().remove(this); closeView(); CDOView[] remainingViews = getViews(); for (CDOView remainingView : remainingViews) { remainingView.close(); } } finally { ((CDORepositoryImpl)repository).closeCheckout(this); } } finally { rootObject = null; view = null; state = State.Closed; } closed = true; } } if (closed) { CDOCheckoutManagerImpl manager = getManager(); if (manager != null) { manager.fireCheckoutOpenEvent(this, oldView, State.Open, State.Closed); } } } private void addView(CDOView view) { synchronized (this) { views.add(view); } } private void removeView(CDOView view) { synchronized (this) { views.remove(view); } } public CDOView[] getViews() { synchronized (this) { return views.toArray(new CDOView[views.size()]); } } public final CDOView getView() { return view; } public final URI getURI() { return uri; } public final EObject getRootObject() { return rootObject; } public final ObjectType getRootType() { return ObjectType.valueFor(rootObject); } protected ResourceSetImpl createResourceSet() { return new ResourceSetImpl(); } public final CDOView openView() { return openView(createResourceSet()); } public CDOView openView(ResourceSet resourceSet) { return openView(readOnly, resourceSet); } public final CDOView openView(boolean readOnly) { return openView(readOnly, createResourceSet()); } public CDOView openView(boolean readOnly, ResourceSet resourceSet) { return openAndConfigureView(readOnly, resourceSet); } public final CDOTransaction openTransaction() { return openTransaction(createResourceSet()); } public CDOTransaction openTransaction(ResourceSet resourceSet) { if (readOnly) { throw new ReadOnlyException("Checkout '" + getLabel() + "' is read-only"); } return (CDOTransaction)openAndConfigureView(false, resourceSet); } private CDOView openAndConfigureView(boolean readOnly, ResourceSet resourceSet) { CDOView view = doOpenView(readOnly, resourceSet); view = configureView(view); EObject object = view.getObject(rootObject); object.eAdapters().add(this); return view; } protected CDOView doOpenView(boolean readOnly, ResourceSet resourceSet) { if (view == null) { return null; } CDOSession session = view.getSession(); if (readOnly) { return session.openView(view, resourceSet); } CDOBranch branch = view.getBranch(); CDOBranchPoint head = branch.getHead(); return session.openTransaction(head, resourceSet); } protected CDOView configureView(final CDOView view) { CDOUtil.configureView(view); ((InternalCDOView)view).setRepositoryName(repository.getLabel()); view.properties().put(CDOView.PROP_TIME_MACHINE_DISABLED, !isReadOnly()); view.properties().put(CHECKOUT_KEY, this); view.addListener(new IListener() { public void notifyEvent(IEvent event) { if (event instanceof CDOViewLocksChangedEvent) { CDOViewLocksChangedEvent e = (CDOViewLocksChangedEvent)event; EObject[] objects = e.getAffectedObjects(); if (objects.length != 0) { CDOCheckoutManagerImpl manager = getManager(); if (manager != null) { manager.fireElementsChangedEvent(objects); } } } else if (event instanceof CDOViewTargetChangedEvent) { CDOViewTargetChangedEvent e = (CDOViewTargetChangedEvent)event; setBranchPoint(e.getBranchPoint()); } else if (event instanceof ILifecycleEvent) { ILifecycleEvent e = (ILifecycleEvent)event; if (e.getKind() == Kind.DEACTIVATED) { removeView(view); } } } }); URI from = URI.createURI("cdo://" + view.getSession().getRepositoryInfo().getUUID() + "/"); URI to = uri.appendSegment(""); view.getResourceSet().getURIConverter().getURIMap().put(from, to); addView(view); return view; } public URI createResourceURI(String path) { String authority = getID(); if (StringUtil.isEmpty(path)) { return URI.createHierarchicalURI(CDOCheckoutViewProvider.SCHEME, authority, null, null, null, null); } String[] segments = new Path(path).segments(); return URI.createHierarchicalURI(CDOCheckoutViewProvider.SCHEME, authority, null, segments, null, null); } public String getEditorOpenerID(CDOID objectID) { synchronized (editorIDs) { String editorID = editorIDs.get(objectID); if (editorID != null) { return editorID; } Properties properties = AbstractManager.loadProperties(getFolder(), EDITOR_PROPERTIES); if (properties != null) { String idString = getCDOIDString(objectID); return properties.getProperty(idString); } return null; } } public void setEditorOpenerID(CDOID objectID, String editorID) { synchronized (editorIDs) { String exisingEditorID = editorIDs.get(objectID); if (ObjectUtil.equals(exisingEditorID, editorID)) { return; } Properties properties = AbstractManager.loadProperties(getFolder(), EDITOR_PROPERTIES); if (properties == null) { properties = new Properties(); } String idString = getCDOIDString(objectID); properties.setProperty(idString, editorID); saveProperties(EDITOR_PROPERTIES, properties); editorIDs.put(objectID, editorID); } } @Override public void delete(boolean deleteContents) { close(); CDOCheckoutManagerImpl manager = getManager(); if (manager != null) { manager.removeElement(this); } super.delete(deleteContents); ((CDORepositoryImpl)repository).removeCheckout(this); repository = null; } @Override public boolean isAdapterForType(Object type) { if (type == CDOCheckout.class) { return true; } return super.isAdapterForType(type); } @Override @SuppressWarnings({ "rawtypes" }) public Object getAdapter(Class adapter) { if (isOpen()) { if (adapter == CDOView.class) { return view; } } return super.getAdapter(adapter); } @Override public String toString() { return getLabel(); } @Override protected void init(File folder, String type, Properties properties) { super.init(folder, type, properties); String repositoryID = properties.getProperty(PROP_REPOSITORY); repository = OM.getRepositoryManager().getElement(repositoryID); if (repository == null) { throw new IllegalStateException("Repository not found: " + repositoryID); } branchID = Integer.parseInt(properties.getProperty(PROP_BRANCH_ID)); branchPath = properties.getProperty(PROP_BRANCH_PATH); branchPoints = properties.getProperty(PROP_BRANCH_POINTS); timeStamp = Long.parseLong(properties.getProperty(PROP_TIME_STAMP)); readOnly = isOnline() ? Boolean.parseBoolean(properties.getProperty(PROP_READ_ONLY)) : false; String property = properties.getProperty(PROP_ROOT_ID); if (property != null) { rootID = CDOIDUtil.read(property); } uri = createResourceURI(null); ((CDORepositoryImpl)repository).addCheckout(this); } @Override protected void collectProperties(Properties properties) { super.collectProperties(properties); properties.setProperty(PROP_REPOSITORY, repository.getID()); properties.setProperty(PROP_BRANCH_ID, Integer.toString(branchID)); properties.setProperty(PROP_TIME_STAMP, Long.toString(timeStamp)); properties.setProperty(PROP_READ_ONLY, Boolean.toString(isOnline() ? readOnly : false)); if (branchPath != null) { properties.setProperty(PROP_BRANCH_PATH, branchPath); } if (branchPoints != null) { properties.setProperty(PROP_BRANCH_POINTS, branchPoints); } if (!CDOIDUtil.isNull(rootID)) { String string = getCDOIDString(rootID); properties.setProperty(PROP_ROOT_ID, string); } } protected IManagedContainer getContainer() { return IPluginContainer.INSTANCE; } protected EObject loadRootObject() { if (CDOIDUtil.isNull(rootID)) { rootID = view.getSession().getRepositoryInfo().getRootResourceID(); } InternalCDOObject cdoObject = (InternalCDOObject)view.getObject(rootID); return cdoObject.cdoInternalInstance(); } protected abstract CDOView openView(CDOSession session); protected void closeView() { view.close(); } public static String getCDOIDString(CDOID id) { StringBuilder builder = new StringBuilder(); CDOIDUtil.write(builder, id); return builder.toString(); } }