blob: 7fc2596397d9c2d611ab18efc11ac678efd8ad64 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2015 itemis 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:
* itemis - Initial API and implementation
*
* </copyright>
*/
package org.eclipse.sphinx.emf.workspace.operations;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.sphinx.emf.Activator;
import org.eclipse.sphinx.emf.splitting.IModelSplitOperation;
import org.eclipse.sphinx.emf.splitting.IModelSplitPolicy;
import org.eclipse.sphinx.emf.splitting.ModelSplitProcessor;
import org.eclipse.sphinx.emf.util.EcorePlatformUtil;
import org.eclipse.sphinx.emf.util.EcoreResourceUtil;
import org.eclipse.sphinx.emf.util.WorkspaceEditingDomainUtil;
import org.eclipse.sphinx.emf.util.WorkspaceTransactionUtil;
import org.eclipse.sphinx.emf.workspace.internal.messages.Messages;
import org.eclipse.sphinx.emf.workspace.loading.ModelLoadManager;
import org.eclipse.sphinx.platform.operations.AbstractLabeledWorkspaceRunnable;
import org.eclipse.sphinx.platform.operations.AbstractWorkspaceOperation;
import org.eclipse.sphinx.platform.operations.ILabeledWorkspaceRunnable;
import org.eclipse.sphinx.platform.util.PlatformLogUtil;
import org.eclipse.sphinx.platform.util.StatusUtil;
public class BasicModelSplitOperation extends AbstractWorkspaceOperation implements IModelSplitOperation {
private Collection<Resource> resources;
private Collection<URI> resourceURIs;
private IModelSplitPolicy modelSplitPolicy;
private boolean deleteOriginalResources = false;
private TransactionalEditingDomain editingDomain = null;
public BasicModelSplitOperation(IModelSplitPolicy modelSplitPolicy) {
super(Messages.operation_splitModel_label);
Assert.isNotNull(modelSplitPolicy);
this.modelSplitPolicy = modelSplitPolicy;
}
public BasicModelSplitOperation(Resource resource, IModelSplitPolicy modelSplitPolicy) {
super(Messages.operation_splitModel_label);
Assert.isNotNull(resource);
Assert.isNotNull(modelSplitPolicy);
getResources().add(resource);
this.modelSplitPolicy = modelSplitPolicy;
}
public BasicModelSplitOperation(URI resourceURI, IModelSplitPolicy modelSplitPolicy) {
super(Messages.operation_splitModel_label);
Assert.isNotNull(resourceURI);
Assert.isNotNull(modelSplitPolicy);
getResourceURIs().add(resourceURI);
this.modelSplitPolicy = modelSplitPolicy;
}
public Collection<Resource> getResources() {
if (resources == null) {
resources = new ArrayList<Resource>();
}
return resources;
}
public Collection<URI> getResourceURIs() {
if (resourceURIs == null) {
resourceURIs = new ArrayList<URI>();
}
return resourceURIs;
}
@Override
public IModelSplitPolicy getModelSplitPolicy() {
return modelSplitPolicy;
}
@Override
public boolean isDeleteOriginalResources() {
return deleteOriginalResources;
}
@Override
public void setDeleteOriginalResources(boolean deleteOriginalResources) {
this.deleteOriginalResources = deleteOriginalResources;
}
@Override
public ISchedulingRule getRule() {
return ResourcesPlugin.getWorkspace().getRoot();
}
protected TransactionalEditingDomain getEditingDomain() {
if (editingDomain == null) {
Collection<Resource> resources = getResources();
if (!resources.isEmpty()) {
editingDomain = TransactionUtil.getEditingDomain(resources.iterator().next());
}
Collection<URI> resourceURIs = getResourceURIs();
if (!resourceURIs.isEmpty()) {
editingDomain = WorkspaceEditingDomainUtil.getEditingDomain(resourceURIs.iterator().next());
}
}
return editingDomain;
}
protected Map<?, ?> getSaveOptions() {
return EcoreResourceUtil.getDefaultSaveOptions();
}
@Override
public void run(IProgressMonitor monitor) throws CoreException {
try {
// TODO Split runnable into separate transactions for loading, splitting & saving and deleting and make sure
// that only split & save operation can be undone under the condition that the original resource are not
// getting deleted
ILabeledWorkspaceRunnable runnable = new AbstractLabeledWorkspaceRunnable(getLabel()) {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
SubMonitor progress = SubMonitor.convert(monitor, 100);
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
ModelSplitProcessor processor = new ModelSplitProcessor(modelSplitPolicy);
try {
// Load model resources to be split
loadResourcesToSplit(processor, progress.newChild(25));
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
// Split model resources
processor.run(progress.newChild(25));
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
// Save resulting model resources
saveSplitResources(processor, progress.newChild(25));
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
// Delete original model resources, if required
if (deleteOriginalResources) {
deleteOriginalResources(progress.newChild(25));
} else {
progress.worked(25);
}
} finally {
processor.dispose();
}
}
};
TransactionalEditingDomain editingDomain = getEditingDomain();
if (editingDomain != null) {
WorkspaceTransactionUtil.executeInWriteTransaction(editingDomain, runnable, monitor);
} else {
runnable.run(monitor);
}
} catch (OperationCanceledException ex) {
throw ex;
} catch (ExecutionException ex) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
throw new CoreException(status);
}
}
protected void loadResourcesToSplit(ModelSplitProcessor processor, IProgressMonitor monitor) {
Assert.isNotNull(processor);
// Make sure that resources behind specified resource URIs are loaded
Collection<URI> resourceURIs = getResourceURIs();
ModelLoadManager.INSTANCE.loadURIs(resourceURIs, false, monitor);
// Add resources corresponding to specified resource URIs to resources to be split
for (URI resourceURI : resourceURIs) {
Resource resource = EcorePlatformUtil.getResource(resourceURI);
if (resource != null) {
resources.add(resource);
}
}
// Add resources to be split to model split processor
processor.getResourcesToSplit().addAll(resources);
}
protected void saveSplitResources(ModelSplitProcessor processor, IProgressMonitor monitor) throws CoreException {
Assert.isNotNull(processor);
// Grab split resources from model split processor and save them
EcorePlatformUtil.saveNewModelResources(getEditingDomain(), processor.getSplitResourceDescriptors(), false, monitor);
}
protected void deleteOriginalResources(IProgressMonitor monitor) throws CoreException {
// TODO Wrap in write transaction and move to new EcorePlatformUtil method
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
Collection<Resource> resources = getResources();
SubMonitor progress = SubMonitor.convert(monitor, resources.size());
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
for (Resource resource : resources) {
// Delete resource if it exists physically, just unload it otherwise
if (EcoreResourceUtil.exists(resource.getURI())) {
try {
resource.delete(Collections.emptyMap());
} catch (IOException ex) {
PlatformLogUtil.logAsError(Activator.getPlugin(), ex);
}
} else {
EcoreResourceUtil.unloadResource(resource);
}
progress.worked(1);
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
}
}
};
// Execute save operation as IWorkspaceRunnable on workspace in order to avoid resource change
// notifications during transaction execution
/*
* !! Important Note !! Setting the IWorkspace.AVOID_UPDATE flag on the outer workspace job or workspace
* runnable from which this method is called doesn't help because the matter of executing a transaction inside
* suppresses its effect.
*/
/*
* !! Important Note !! Only set IWorkspace.AVOID_UPDATE flag but don't define any scheduling restrictions for
* the save operation right here (this must only be done on the outer workspace job or workspace runnable from
* which this method is called). Otherwise it would be likely to end up in deadlocks with operations which
* already have acquired exclusive access to the workspace but are waiting for exclusive access to the model
* (i.e. for the transaction).
*/
ResourcesPlugin.getWorkspace().run(runnable, null, IWorkspace.AVOID_UPDATE, monitor);
}
}