blob: 36ede4687524aac0c654846f2d795cfe8a178886 [file] [log] [blame]
/***************************************************************************************************
* Copyright (c) 2003, 2004 IBM Corporation 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: IBM Corporation - initial API and implementation
**************************************************************************************************/
package org.eclipse.wst.common.frameworks.internal.operations;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import org.eclipse.core.internal.runtime.Assert;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
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.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.wst.common.frameworks.internal.AdaptabilityUtility;
import org.eclipse.wst.common.frameworks.internal.WTPResourceHandler;
import org.eclipse.wst.common.frameworks.internal.enablement.IEnablementManager;
import org.eclipse.wst.common.frameworks.internal.enablement.nonui.WFTWrappedException;
import org.eclipse.wst.common.frameworks.internal.plugin.WTPCommonPlugin;
//TODO perhaps this class should be subclassed from a new super class which more closely resembles org.eclipse.ui.actions.WorkspaceModifyOperation
/**
* An operation which potentially makes changes to the workspace. All resource modification should
* be performed using this operation. The primary consequence of using this operation is that events
* which typically occur as a result of workspace changes (such as the firing of resource deltas,
* performance of autobuilds, etc.) are deferred until the outermost operation has successfully
* completed.
* <p>
* Subclasses must implement <code>execute</code> to do the work of the operation.
* </p>
* This class is EXPERIMENTAL and is subject to substantial changes.
*/
public abstract class WTPOperation implements IHeadlessRunnableWithProgress {
/**
* The dataModel used to execute this operation
*/
protected WTPOperationDataModel operationDataModel;
private OperationStatus opStatus;
private String id;
/**
* Constructor for the operation. Clients should use this constructor instead of the no argument
* constructor.
*
* @param operationDataModel
*/
public WTPOperation(WTPOperationDataModel operationDataModel) {
setOperationDataModel(operationDataModel);
}
/**
* This no argument constructor should not be used by clients. This is for extended operations.
*
* ExtendedOperations
*/
public WTPOperation() {
}
//TODO see if this can be made package visible only.
/**
* Note: This method is for internal use only. Clients should not call this method.
*
* ExtendedOperations
*
* @param value
* the operation's id
*/
public final void setID(String value) {
Assert.isTrue(this.id == null, WTPResourceHandler.getString("22")); //$NON-NLS-1$
Assert.isNotNull(value, WTPResourceHandler.getString("23")); //$NON-NLS-1$
this.id = value;
}
//TODO see if this can be removed
/**
* Note: This method is for internal use only. Clients should not call this method.
*
* ExtendedOperations
*
* @return the operation's id
*/
public final String getID() {
return this.id;
}
//TODO see if this can be make package visible only.
/**
* Note: This method is for internal use only. Clients should not call this method.
*
* @param operationDataModel
*/
public final void setOperationDataModel(WTPOperationDataModel operationDataModel) {
this.operationDataModel = operationDataModel;
}
/**
* Returns the dataModels used by this operation.
*
* @return the dataModels used by this operation.
*/
public final WTPOperationDataModel getOperationDataModel() {
return operationDataModel;
}
/**
* Returns the result status of this operation. If this operation net been executed, then it
* will return null.
*
* @return he result status of this operation.
*/
public IStatus getStatus() {
if (null == opStatus)
return WTPCommonPlugin.OK_STATUS;
return opStatus;
}
/**
* Performs the steps that are to be treated as a single logical workspace change.
* <p>
* Subclasses must implement this method.
* </p>
*
* @param monitor
* the progress monitor to use to display progress and field user requests to cancel
* @exception CoreException
* if the operation fails due to a CoreException
* @exception InvocationTargetException
* if the operation fails due to an exception other than CoreException
* @exception InterruptedException
* if the operation detects a request to cancel, using
* <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
* <code>InterruptedException</code>
*/
protected abstract void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException;
/**
* Subclasses should override as necessary to perform any additional initialization before
* executing.
*
* @param monitor
* the progress monitor
*/
protected void initilize(IProgressMonitor monitor) {
//Making sure the status objects are initialized
// status = null;
opStatus = null;
}
private ComposedExtendedOperationHolder initializeExtensionOperations() {
return OperationExtensionRegistry.getExtensions(this);
}
/**
* <p>
* Subclasses should override as necessary to perform any post executiong cleanup. Subclasses,
* should not initialize any resources anywhere other than within either the
* initialize(IProjgressMonitor) or the execute(IProgressMonitor) methods. This method will
* always be called when an operation is run irregardless of whether the execution is
* successful.
* </p>
* <p>
* Clients should never need to call this method
* </p>
*
* @param monitor
* the progress monitor
*/
protected void dispose(IProgressMonitor monitor) {
}
//TODO see if this can be removed
/**
* Note: This method is for internal use only. Clients should not call this method.
*
* @return the workspace
*/
protected IWorkspace getWorkspace() {
return ResourcesPlugin.getWorkspace();
}
//TODO see if this can be removed
/**
* Note: This method is for internal use only. Clients should not call this method.
*
* @param op
* the to append
* @return A new operation
*/
public WTPOperation append(WTPOperation op) {
ComposedOperation composedOp = new ComposedOperation();
composedOp.addRunnable(this);
composedOp.addRunnable(op);
return composedOp;
}
/**
* Initiates a batch of changes, by invoking the execute() method as a workspace runnable.
* Client should always call this method and never call the execute() method.
*
* @param monitor
* the progress monitor to use to display progress
* @exception InvocationTargetException
* wraps any CoreException, runtime exception or error thrown by the execute()
* method
* @see WorkspaceModifyOperation - this class was directly copied from it
*/
public synchronized final void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
final InvocationTargetException[] iteHolder = new InvocationTargetException[1];
try {
IWorkspaceRunnable workspaceRunnable = new IWorkspaceRunnable() {
public void run(IProgressMonitor pm) throws CoreException {
try {
doRun(pm);
if (opStatus != null && !opStatus.isOK()) {
//TODO do something with the status
System.out.println(opStatus.getMessage());
}
} catch (InvocationTargetException e) {
// Pass it outside the workspace runnable
iteHolder[0] = e;
} catch (InterruptedException e) {
// Re-throw as OperationCanceledException, which will
// be
// caught and re-thrown as InterruptedException below.
throw new OperationCanceledException(e.getMessage());
}
}
};
ISchedulingRule rule = getSchedulingRule();
if (rule == null)
ResourcesPlugin.getWorkspace().run(workspaceRunnable, monitor);
else
ResourcesPlugin.getWorkspace().run(workspaceRunnable, rule, 0, monitor);
} catch (CoreException e) {
if (e.getStatus().getCode() == IResourceStatus.OPERATION_FAILED)
throw new WFTWrappedException(e.getStatus().getException(), e.getMessage());
throw new WFTWrappedException(e);
} catch (OperationCanceledException e) {
throw new InterruptedException(e.getMessage());
}
// Re-throw the InvocationTargetException, if any occurred
if (iteHolder[0] != null) {
throw new WFTWrappedException(iteHolder[0].getTargetException(), iteHolder[0].getMessage());
}
}
/**
* @return
*/
protected ISchedulingRule getSchedulingRule() {
return null;
}
/**
* Typically clients should call the run(IProgressMonitor) method instead of this method. This
* may be used by subclassed operations during execution to invoke other operations. This method
* runs within the same the same WorkspaceRunnable as the calling operation.
*
* @param monitor
* the progress monitor to use to display progress
* @throws CoreException
* @throws InvocationTargetException
* @throws InterruptedException
*/
public final void doRun(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException {
boolean alreadyLocked = operationDataModel == null ? true : operationDataModel.isLocked();
boolean operationValidationEnabled = operationDataModel == null ? false : operationDataModel.isOperationValidationEnabled();
try {
if (!alreadyLocked) {
operationDataModel.setLocked(true);
}
if (operationValidationEnabled) {
operationDataModel.setOperationValidationEnabled(false);
IStatus status = operationDataModel.validateDataModel();
if (!status.isOK()) {
//TODO display something to user and remove System.out
System.out.println(WTPResourceHandler.getString("24", new Object[]{status.getMessage()})); //$NON-NLS-1$
Thread.dumpStack();
return;
}
}
initilize(monitor);
if (!validateEdit())
return;
//if (getStatus().isOK()) {
ComposedExtendedOperationHolder extOpHolder = initializeExtensionOperations();
IStatus preOpStatus = runPreOps(monitor, extOpHolder);
execute(monitor);
IStatus postOpStatus = runPostOps(monitor, extOpHolder);
if (null != preOpStatus)
addExtendedStatus(preOpStatus);
if (null != postOpStatus)
addExtendedStatus(postOpStatus);
//}
} finally {
dispose(monitor);
if (!alreadyLocked) {
operationDataModel.setLocked(false);
}
if (operationValidationEnabled) {
operationDataModel.setOperationValidationEnabled(true);
}
}
// CoreException and OperationCanceledException are propagated
}
/**
* This method is called when an operation is run after initialize() is called, but before
* execute(). If this method returns <code>true</code> then operation's execution continues,
* otherwise, execution is aborted. Subclasses should override this method to provide any final
* initialization and validation required before execution.
*
* @return
*/
protected boolean validateEdit() {
return true;
}
//TODO lock down addStatus so it throws runtime exceptions if not called during execute.
//TODO make this protected and create a package level accessor for WTPOperationJobAdapter
/**
* <p>
* Adds a status to this opererations status. If this operation currently has no status, then
* the specified status becomes the operation's status. If the operation already has a status,
* then that status is converted to a multistatus and the specified status is appendend to it.
* </p>
* <p>
* This method should only be called from the execute() method of subclasses.
* </p>
*
* @param aStatus
* the status to add.
*/
public final void addStatus(IStatus aStatus) {
if (opStatus == null) {
opStatus = new OperationStatus(aStatus.getMessage(), aStatus.getException());
opStatus.setSeverity(aStatus.getSeverity());
opStatus.add(aStatus);
} else {
opStatus.add(aStatus);
}
}
/**
* This is to keep track of extended operation stati. If the main status is WARNING, an extended
* status of ERROR will not make the main status ERROR. If the main status is OK, an extended
* status of ERROR or WARNING will make the main status WARNING.
*
* @param aStatus
*/
private void addExtendedStatus(IStatus aStatus) {
if (opStatus == null) {
opStatus = new OperationStatus(new IStatus[]{WTPCommonPlugin.OK_STATUS});
}
opStatus.addExtendedStatus(aStatus);
}
private IStatus runPostOps(IProgressMonitor pm, ComposedExtendedOperationHolder extOpHolder) {
IStatus postOpStatus = null;
if ((extOpHolder != null) && extOpHolder.hasPostOps()) {
postOpStatus = runExtendedOps(extOpHolder.getPostOps(), pm);
}
return postOpStatus;
}
private IStatus runPreOps(IProgressMonitor pm, ComposedExtendedOperationHolder extOpHolder) {
IStatus preOpStatus = null;
if ((extOpHolder != null) && extOpHolder.hasPreOps()) {
preOpStatus = runExtendedOps(extOpHolder.getPreOps(), pm);
}
return preOpStatus;
}
private IStatus runExtendedOps(List opList, IProgressMonitor pm) {
WTPOperation op = null;
OperationStatus returnStatus = null;
IStatus localStatus;
String opId = null;
for (int i = 0; i < opList.size(); i++) {
op = (WTPOperation) opList.get(i);
try {
opId = op.getID();
boolean shouldExtendedRun = true;
List extendedContext = (List) operationDataModel.getProperty(WTPOperationDataModel.EXTENDED_CONTEXT);
for (int contextCount = 0; shouldExtendedRun && contextCount < extendedContext.size(); contextCount++) {
IProject project = (IProject) AdaptabilityUtility.getAdapter(extendedContext.get(contextCount), IProject.class);
if (null != project && !IEnablementManager.INSTANCE.getIdentifier(opId, project).isEnabled()) {
shouldExtendedRun = false;
}
}
if (shouldExtendedRun) {
op.setOperationDataModel(operationDataModel);
op.doRun(new SubProgressMonitor(pm, IProgressMonitor.UNKNOWN));
localStatus = op.getStatus();
} else
localStatus = null;
} catch (Exception e) {
localStatus = new Status(IStatus.ERROR, WTPCommonPlugin.PLUGIN_ID, 0, WTPResourceHandler.getString("25", new Object[]{op.getClass().getName()}), e); //$NON-NLS-1$
}
if (localStatus != null) {
if (returnStatus == null) {
returnStatus = new OperationStatus(new IStatus[]{localStatus});
} else {
returnStatus.add(localStatus);
}
}
}
return returnStatus;
}
// protected WTPOperation getRootExtendedOperation() {
// if (this.getRootExtendedOperation() == null)
// return this;
// return this.getRootExtendedOperation();
// }
//TODO this should be deleted.
/**
* Note: This method is for internal use only. Clients should not call this method.
*/
protected static final void runNestedDefaultOperation(WTPOperationDataModel model, IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
WTPOperation op = model.getDefaultOperation();
if (op != null)
op.run(monitor);
}
}