blob: 6be4cc3a92fa9f8b45acf5cd4c69d50e3226ba45 [file] [log] [blame]
/*
* Copyright (c) 2015, 2016, 2019, 2020 Eike Stepper (Loehne, 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<>();
private final Map<CDOID, String> editorIDs = new WeakHashMap<>();
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();
}
@Override
public final CDORepository getRepository()
{
return repository;
}
@Override
public final int getBranchID()
{
return branchID;
}
@Override
public final void setBranchID(int branchID)
{
setBranchPoint(branchID, timeStamp);
}
@Override
public String getBranchPath()
{
return branchPath;
}
public final void setBranchPath(String branchPath)
{
if (!ObjectUtil.equals(this.branchPath, branchPath))
{
this.branchPath = branchPath;
save();
}
}
@Override
public final CDOBranchPoint getBranchPoint()
{
return view;
}
@Override
public final void setBranchPoint(CDOBranchPoint branchPoint)
{
int branchID = branchPoint.getBranch().getID();
long timeStamp = branchPoint.getTimeStamp();
setBranchPoint(branchID, timeStamp);
}
@Override
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();
}
}
@Override
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();
}
@Override
public CDOBranchPoint[] getBranchPoints()
{
if (branchPoints != null && isOpen())
{
List<CDOBranchPoint> result = new ArrayList<>();
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;
}
@Override
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;
}
@Override
public final long getTimeStamp()
{
return timeStamp;
}
@Override
public final void setTimeStamp(long timeStamp)
{
setBranchPoint(branchID, timeStamp);
}
@Override
public final boolean isReadOnly()
{
return readOnly;
}
@Override
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();
}
}
@Override
public final CDOID getRootID()
{
return rootID;
}
@Override
public final void setRootID(CDOID rootID)
{
this.rootID = rootID;
}
@Override
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()));
}
@Override
public final State getState()
{
return state;
}
@Override
public final boolean isOpen()
{
return view != null;
}
@Override
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.
}
@Override
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);
}
}
@Override
public CDOView[] getViews()
{
synchronized (this)
{
return views.toArray(new CDOView[views.size()]);
}
}
@Override
public final CDOView getView()
{
return view;
}
@Override
public final URI getURI()
{
return uri;
}
@Override
public final EObject getRootObject()
{
return rootObject;
}
@Override
public final ObjectType getRootType()
{
return ObjectType.valueFor(rootObject);
}
protected ResourceSetImpl createResourceSet()
{
return new ResourceSetImpl();
}
@Override
public final CDOView openView()
{
return openView(createResourceSet());
}
@Override
public CDOView openView(ResourceSet resourceSet)
{
return openView(readOnly, resourceSet);
}
@Override
public final CDOView openView(boolean readOnly)
{
return openView(readOnly, createResourceSet());
}
@Override
public CDOView openView(boolean readOnly, ResourceSet resourceSet)
{
return openAndConfigureView(readOnly, resourceSet);
}
@Override
public final CDOTransaction openTransaction()
{
return openTransaction(createResourceSet());
}
@Override
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()
{
@Override
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;
}
@Override
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);
}
@Override
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;
}
}
@Override
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();
}
}