| /******************************************************************************* |
| * 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); |
| |
| } |
| |
| } |