| /*************************************************************************************************** |
| * 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.Collections; |
| import java.util.Hashtable; |
| 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; |
| |
| /** |
| * replace with {@link org.eclipse.wst.common.frameworks.datamodel.IDataModelOperation} |
| * 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 { |
| |
| private static Hashtable threadToExtendedOpControl; |
| |
| private class ExtendedOpControl { |
| private boolean allowExtensions; |
| List restrictedExtensions; |
| |
| public ExtendedOpControl(boolean allowExtensions, List restrictedExtensions) { |
| this.allowExtensions = allowExtensions; |
| this.restrictedExtensions = restrictedExtensions; |
| } |
| |
| public boolean shouldExecute(String operationID) { |
| return allowExtensions && !restrictedExtensions.contains(operationID); |
| } |
| } |
| |
| /** |
| * 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 initialize(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 { |
| if (null == threadToExtendedOpControl) { |
| threadToExtendedOpControl = new Hashtable(); |
| } |
| final Thread currentThread = Thread.currentThread(); |
| final boolean rootOperation = !threadToExtendedOpControl.containsKey(currentThread); |
| |
| boolean alreadyLocked = operationDataModel == null ? true : operationDataModel.isLocked(); |
| boolean operationValidationEnabled = operationDataModel == null ? false : operationDataModel.isOperationValidationEnabled(); |
| try { |
| if (rootOperation) { |
| boolean allowExtensions = operationDataModel == null ? true : operationDataModel.getBooleanProperty(WTPOperationDataModel.ALLOW_EXTENSIONS); |
| List restrictedExtensions = operationDataModel == null ? Collections.EMPTY_LIST : (List) operationDataModel.getProperty(WTPOperationDataModel.RESTRICT_EXTENSIONS); |
| ExtendedOpControl extendedOpControl = new ExtendedOpControl(allowExtensions, restrictedExtensions); |
| threadToExtendedOpControl.put(currentThread, extendedOpControl); |
| } |
| 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; |
| } |
| } |
| initialize(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 (rootOperation) { |
| threadToExtendedOpControl.remove(currentThread); |
| } |
| 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; |
| ExtendedOpControl opControl = (ExtendedOpControl) threadToExtendedOpControl.get(Thread.currentThread()); |
| for (int i = 0; i < opList.size(); i++) { |
| op = (WTPOperation) opList.get(i); |
| opId = op.getID(); |
| if (opControl.shouldExecute(op.getClass().getName()) && opControl.shouldExecute(opId)) { |
| try { |
| 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); |
| } |
| } |