blob: 8d14b0a7c409525fd6196d67f6cb4c353933c1ce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2012 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eugen Neufeld - initial API and implementation
* Edgar Mueller - Bug 440798: EMFStoreProvider project cloning isn't transaction-friendly
*******************************************************************************/
package org.eclipse.emf.ecp.emfstore.core.internal;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.core.ECPProject;
import org.eclipse.emf.ecp.core.ECPRepository;
import org.eclipse.emf.ecp.core.util.ECPContainer;
import org.eclipse.emf.ecp.core.util.ECPModelContextProvider;
import org.eclipse.emf.ecp.core.util.ECPUtil;
import org.eclipse.emf.ecp.spi.core.DefaultProvider;
import org.eclipse.emf.ecp.spi.core.InternalProject;
import org.eclipse.emf.ecp.spi.core.InternalRepository;
import org.eclipse.emf.ecp.spi.core.util.InternalChildrenList;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.ItemPropertyDescriptor;
import org.eclipse.emf.emfstore.client.ESLocalProject;
import org.eclipse.emf.emfstore.client.ESRemoteProject;
import org.eclipse.emf.emfstore.client.ESServer;
import org.eclipse.emf.emfstore.client.ESWorkspace;
import org.eclipse.emf.emfstore.client.util.ESVoidCallable;
import org.eclipse.emf.emfstore.client.util.RunESCommand;
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.ProjectSpace;
import org.eclipse.emf.emfstore.internal.client.model.impl.ProjectSpaceImpl;
import org.eclipse.emf.emfstore.internal.client.model.impl.api.ESLocalProjectImpl;
import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreClientUtil;
import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreCommand;
import org.eclipse.emf.emfstore.internal.client.observers.OperationObserver;
import org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection;
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.IdEObjectCollectionChangeObserver;
import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AbstractOperation;
import org.eclipse.emf.emfstore.server.exceptions.ESException;
/**
* This is the EMFStore Provider for ECP.
*
* @author Eugen Neufeld
*/
public final class EMFStoreProvider extends DefaultProvider {
/**
* This is the name of the EMFStore Provider.
*/
public static final String NAME = "org.eclipse.emf.ecp.emfstore.provider"; //$NON-NLS-1$
/**
* EMFStore Provider Singleton.
*
* @deprecated use ECPUtil.getECPProviderRegistry().getProvider(EMFStoreProvider.NAME) instead
*/
@Deprecated
public static EMFStoreProvider INSTANCE;
/**
* Property constant for Repository URL.
*/
public static final String PROP_REPOSITORY_URL = "repositoryUrl"; //$NON-NLS-1$
/**
* Property constant for Repository Port.
*/
public static final String PROP_PORT = "port"; //$NON-NLS-1$
/**
* Property constant for Repository Certificate.
*/
public static final String PROP_CERTIFICATE = "certificate"; //$NON-NLS-1$
/**
* Property constant for ProjectSpaceID.
*/
public static final String PROP_PROJECTSPACEID = "projectSpaceID"; //$NON-NLS-1$
/**
* Property constant for ServerInfoID.
*/
public static final String PROP_SERVERINFOID = "serverInfoID"; //$NON-NLS-1$
private AdapterImpl adapter;
/**
* Default constructor.
*/
public EMFStoreProvider() {
super(NAME);
configureEMFStore();
INSTANCE = this;
}
/**
*
*/
private void configureEMFStore() {
Configuration.getClientBehavior().setAutoSave(false);
}
/** {@inheritDoc} */
@Override
public EditingDomain createEditingDomain(final InternalProject project) {
final EditingDomain domain = ECPEMFUtils.getESWorkspaceProviderInstance().getWorkspace().toInternalAPI()
.getEditingDomain();
return domain;
}
/** {@inheritDoc} */
@Override
public void fillChildren(final ECPContainer context, final Object parent, final InternalChildrenList childrenList) {
if (parent instanceof InternalProject) {
final ESLocalProject projectSpace = getProjectSpace((InternalProject) parent);
if (projectSpace != null) {
childrenList.addChildren(projectSpace.getModelElements());
}
} else if (parent instanceof InternalRepository) {
final ESServer serverInfo = getServerInfo((InternalRepository) parent);
if (serverInfo.getLastUsersession() != null && serverInfo.getLastUsersession().isLoggedIn()) {
try {
final List<ESRemoteProject> projectInfos = serverInfo.getRemoteProjects(serverInfo
.getLastUsersession());
for (final ESRemoteProject projectInfo : projectInfos) {
childrenList.addChild(new EMFStoreProjectWrapper((InternalRepository) parent, projectInfo));
}
} catch (final ESException e) {
Activator.log(e);
}
}
}
RunESCommand.run(new Callable<Void>() {
@Override
public Void call() throws Exception {
EMFStoreProvider.super.fillChildren(context, parent, childrenList);
return null;
}
});
}
@Override
public boolean hasCreateProjectWithoutRepositorySupport() {
return true;
}
/** {@inheritDoc} */
@Override
public EList<? extends Object> getElements(InternalProject project) {
return getProjectSpace(project).getModelElements();
}
/** {@inheritDoc} */
@Override
public void handleLifecycle(ECPContainer context, LifecycleEvent event) {
switch (event) {
case INIT:
handleInit(context);
break;
case DISPOSE:
handelDispose(context);
break;
case CREATE:
handleCreate(context);
break;
case REMOVE:
handleRemove(context);
break;
default:
break;
}
final String providerClass = getClass().getSimpleName();
final String contextClass = context.getClass().getSimpleName();
Activator.log(IStatus.INFO, providerClass + " received " + event + " for " + contextClass + " " + context); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Called to handle the remove operation on an {@link ECPContainer}.
*
* @param context the {@link ECPContainer} to remove
*/
private void handleRemove(ECPContainer context) {
if (context instanceof InternalProject) {
final InternalProject project = (InternalProject) context;
final ESLocalProject ps = (ESLocalProject) project.getProviderSpecificData();
if (ps != null) {
try {
ps.delete(new NullProgressMonitor());
} catch (final ESException ex) {
Activator.log(ex);
} catch (final IOException ex) {
Activator.log(ex);
}
project.setProviderSpecificData(null);
}
}
}
private void handleCreate(final ECPContainer context) {
if (context instanceof InternalRepository) {
getServerInfo((InternalRepository) context);
} else if (context instanceof InternalProject) {
getProjectSpace((InternalProject) context);
}
}
private void handelDispose(ECPContainer context) {
if (context instanceof InternalProject) {
final ESLocalProject projectSpace = getProjectSpace((InternalProject) context);
if (projectSpace != null) {
((ESLocalProjectImpl) projectSpace).toInternalAPI().getProject().eAdapters().remove(adapter);
}
}
}
private boolean isAutosave() {
final IPreferencesService service = Platform.getPreferencesService();
return service.getBoolean("org.eclipse.emf.ecp", "AUTOSAVE", false, null); //$NON-NLS-1$ //$NON-NLS-2$
}
private void handleInit(final ECPContainer context) {
if (context instanceof InternalProject) {
final ESLocalProject localProject = getProjectSpace((InternalProject) context, true);
if (localProject == null) {
return;
}
final ProjectSpace projectSpace = ((ESLocalProjectImpl) localProject).toInternalAPI();
if (isAutosave()) {
// TODO EMFStore how to listen to operations?
ESWorkspaceProviderImpl.getObserverBus().register(new OperationObserver() {
@Override
public void operationUndone(ProjectSpace projectSpace, AbstractOperation operation) {
doSave((InternalProject) context);
}
@Override
public void operationExecuted(ProjectSpace projectSpace, AbstractOperation operation) {
doSave((InternalProject) context);
}
});
}
// TODO EMFStore how to add IdEObjectCollection Observer?
projectSpace.getProject().addIdEObjectCollectionChangeObserver(new IdEObjectCollectionChangeObserver() {
// 2
@Override
public void notify(Notification notification, IdEObjectCollection collection, EObject modelElement) {
notifyProviderChangeListeners(notification);
if (modelElement instanceof ProjectImpl) {
final ProjectSpaceImpl projectSpace = (ProjectSpaceImpl) modelElement.eContainer();
final ECPProject ecpProject = getProject(projectSpace.toAPI());
((InternalProject) context).notifyObjectsChanged(
Collections.singleton((Object) ecpProject), true);
} else {
((InternalProject) context).notifyObjectsChanged(
Collections.singleton((Object) modelElement), true);
}
}
// 3
@Override
public void modelElementRemoved(IdEObjectCollection collection, EObject modelElement) {
if (modelElement.eContainer() == null) {
((InternalProject) context).notifyObjectsChanged(Arrays.asList(context, modelElement), true);
}
}
// 1
@Override
public void modelElementAdded(IdEObjectCollection collection, EObject modelElement) {
if (Project.class.isInstance(modelElement.eContainer())) {
((InternalProject) context).notifyObjectsChanged(Arrays.asList(modelElement, context), true);
}
((InternalProject) context).notifyObjectsChanged(Collections.singleton((Object) modelElement),
true);
}
@Override
public void collectionDeleted(IdEObjectCollection collection) {
// project delete
((InternalProject) context).notifyObjectsChanged(Collections.singleton((Object) context), true);
}
});
}
}
/** {@inheritDoc} */
@Override
public Iterator<EObject> getLinkElements(InternalProject project, EObject modelElement, EReference eReference) {
final Collection<EObject> result = new HashSet<EObject>();
final EClass elementClass = modelElement.eClass();
EClassifier type = EcoreUtil.getReifiedType(elementClass, eReference.getEGenericType()).getEClassifier();
if (type == null) {
type = eReference.getEType();
}
// TODO EMFStore does it work with ESLocalProject?
final ProjectSpace projectSpace = ((ESLocalProjectImpl) getProjectSpace(project)).toInternalAPI();
ItemPropertyDescriptor.collectReachableObjectsOfType(new HashSet<EObject>(), result, projectSpace.getProject(),
type);
return result.iterator();
}
/**
* @param info
* @param ecpProperties
* @return
*/
private boolean isSameServerInfo(ESServer info, String url, int port, String certificate) {
return info.getURL().equalsIgnoreCase(url) && info.getPort() == port
&& info.getCertificateAlias().equalsIgnoreCase(certificate);
}
/** {@inheritDoc} */
@Override
public void doSave(InternalProject project) {
getProjectSpace(project).save();
}
/** {@inheritDoc} */
@Override
public boolean isDirty(InternalProject project) {
final ESLocalProject projectSpace = getProjectSpace(project);
if (projectSpace != null) {
return projectSpace.hasUnsavedChanges();
}
return false;
}
/** {@inheritDoc} */
@Override
public void cloneProject(final InternalProject projectToClone, InternalProject targetProject) {
final ProjectSpace toClone = ((ESLocalProjectImpl) getProjectSpace(projectToClone)).toInternalAPI();
final ProjectSpace target = ((ESLocalProjectImpl) getProjectSpace(targetProject)).toInternalAPI();
RunESCommand.run(new ESVoidCallable() {
@Override
public void run() {
target.setProject(ModelUtil.clone(toClone.getProject()));
}
});
}
/** {@inheritDoc} */
@Override
public boolean modelExists(InternalProject project) {
return getProjectSpace(project, false) != null;
}
/** {@inheritDoc} */
@Override
public Notifier getRoot(InternalProject project) {
// TODO EMFStore other way to get root of localproject?
return ((ESLocalProjectImpl) getProjectSpace(project)).toInternalAPI().getProject();
}
@Override
public boolean contains(InternalProject project, Object object) {
if (!EObject.class.isInstance(object)) {
return false;
}
final ESLocalProject projectSpace = getProjectSpace(project);
return projectSpace.contains((EObject) object);
}
@Override
public ECPContainer getModelContext(Object element) {
if (element instanceof ECPContainer) {
return (ECPContainer) element;
}
if (element instanceof ECPModelContextProvider) {
return ((ECPModelContextProvider) element).getModelContext(element);
}
if (element instanceof EObject) {
final EObject eObject = (EObject) element;
ProjectSpace ps = null;
try {
ps = ESWorkspaceProviderImpl.getProjectSpace(eObject);
} catch (final IllegalArgumentException iae) {
return null;
}
if (ps != null) {
final ECPContainer context = getModelContextFromAdapter(ps.getProject());
if (context != null) {
return context;
}
}
element = eObject.eResource();
}
if (element instanceof Resource) {
final Resource resource = (Resource) element;
final ECPContainer context = getModelContextFromAdapter(resource);
if (context != null) {
return context;
}
element = resource.getResourceSet();
}
if (element instanceof ResourceSet) {
final ResourceSet resourceSet = (ResourceSet) element;
final ECPContainer context = getModelContextFromAdapter(resourceSet);
if (context != null) {
return context;
}
}
return null;
}
/**
* This retrieves the {@link ProjectSpace} from an {@link InternalProject}.
* First it checks whether the {@link InternalProject} has a ProjectSpaceID attached.
* If an ID is attached, a ProjectSpace is searched with this ID.
* If no ID is attached or now ProjectSpace was found a LocalProject is created.
*
* @param internalProject the project to get the ProjectSpace for
* @return the corresponding ProjectSpace
*/
public ESLocalProject getProjectSpace(InternalProject internalProject) {
return getProjectSpace(internalProject, false);
}
private ESLocalProject getProjectSpace(InternalProject internalProject, boolean createNewIfNeeded) {
ESLocalProject projectSpace = (ESLocalProject) internalProject.getProviderSpecificData();
if (projectSpace == null) {
boolean found = false;
final List<ESLocalProject> localProjects = ECPEMFUtils.getESWorkspaceProviderInstance().getWorkspace()
.getLocalProjects();
for (final ESLocalProject localProject : localProjects) {
final String projectSpaceID = internalProject.getProperties().getValue(
EMFStoreProvider.PROP_PROJECTSPACEID);
if (localProject.getLocalProjectId().getId().equals(projectSpaceID)) {
found = true;
projectSpace = localProject;
break;
}
}
if (!found && createNewIfNeeded) {
projectSpace = ECPEMFUtils.getESWorkspaceProviderInstance().getWorkspace()
.createLocalProject(internalProject.getName());
internalProject.getProperties().addProperty(EMFStoreProvider.PROP_PROJECTSPACEID,
projectSpace.getLocalProjectId().getId());
}
internalProject.setProviderSpecificData(projectSpace);
}
return projectSpace;
}
/**
* This retrieves the {@link org.eclipse.emf.emfstore.internal.client.model.ServerInfo} from an
* {@link InternalRepository}.
* First it checks whether the {@link InternalRepository} has a ServerInfoID attached.
* If an ID is attached, a ServerInfo is searched with this ID.
* If no ID is attached or now ServerInfo was found a default ServerInfo is created.
*
* @param internalRepository the repository to get the ServerInfo for
* @return the corresponding ServerInfo
*/
public ESServer getServerInfo(InternalRepository internalRepository) {
ESServer serverInfo = (ESServer) internalRepository.getProviderSpecificData();
if (serverInfo == null) {
final ESWorkspace workspace = ECPEMFUtils.getESWorkspaceProviderInstance().getWorkspace();
boolean foundExisting = false;
for (final ESServer info : workspace.getServers()) {
if (internalRepository.getProperties().hasProperties()
&& isSameServerInfo(info,
internalRepository.getProperties().getValue(EMFStoreProvider.PROP_REPOSITORY_URL),
Integer.parseInt(internalRepository.getProperties().getValue(EMFStoreProvider.PROP_PORT)),
internalRepository.getProperties().getValue(EMFStoreProvider.PROP_CERTIFICATE))) {
serverInfo = info;
foundExisting = true;
break;
}
}
if (!foundExisting && internalRepository.getProperties().hasProperties()) {
serverInfo = EMFStoreClientUtil.createServerInfo(
internalRepository.getProperties().getValue(EMFStoreProvider.PROP_REPOSITORY_URL),
Integer.parseInt(internalRepository.getProperties().getValue(EMFStoreProvider.PROP_PORT)),
internalRepository.getProperties().getValue(EMFStoreProvider.PROP_CERTIFICATE)).toAPI();
workspace.addServer(serverInfo);
} else if (!foundExisting && !internalRepository.getProperties().hasProperties()) {
serverInfo = EMFStoreClientUtil.giveServerInfo("localhost", 8080).toAPI(); //$NON-NLS-1$
}
internalRepository.setProviderSpecificData(serverInfo);
}
return serverInfo;
}
/**
* This gets the ECPProject based on a ProjectSpace.
*
* @param projectSpace the {@link ProjectSpace} to get the {@link ECPProject} for
* @return the {@link ECPProject} corresponding to this ProjectSpace or null if none found
*/
public ECPProject getProject(ESLocalProject projectSpace) {
for (final InternalProject project : getOpenProjects()) {
final ESLocalProject localProjectSpace = (ESLocalProject) project.getProviderSpecificData();
if (localProjectSpace.equals(projectSpace)) {
return project;
}
}
return null;
}
/**
* This gets the ECPRepository based on a ServerInfo.
*
* @param serverInfo the {@link ESServer} to get the {@link ECPRepository} for
* @return the {@link ECPRepository} corresponding to this ServerInfo or null if none found
*/
public ECPRepository getRepository(ESServer serverInfo) {
if (serverInfo != null) {
for (final ECPRepository internalRepository : ECPUtil.getECPRepositoryManager().getRepositories()) {
if (internalRepository.getProvider().equals(this)) {
if (serverInfo.equals(((InternalRepository) internalRepository).getProviderSpecificData())) {
return internalRepository;
}
}
}
}
return null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.spi.core.InternalProvider#isThreadSafe()
*/
@Override
public boolean isThreadSafe() {
return false;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.spi.core.DefaultProvider#doDelete(org.eclipse.emf.ecp.spi.core.InternalProject,
* java.util.Collection)
*/
@Override
public void doDelete(InternalProject project, final Collection<Object> objects) {
final ProjectSpace projectSpace = ((ESLocalProjectImpl) getProjectSpace(project)).toInternalAPI();
// TODO EMFStore how to delete eObject?
new EMFStoreCommand() {
@Override
protected void doRun() {
for (final Object object : objects) {
if (EObject.class.isInstance(object)) {
projectSpace.getProject().deleteModelElement((EObject) object);
}
}
}
}.run(false);
}
}