/******************************************************************************* * Copyright (c) 2010 Wind River Systems 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.dsf.concurrent; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.cdt.dsf.internal.DsfPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; /** * A type of {@link Sequence} which uses reflection and annotations to * declare its different {@link Sequence.Step}. It can be used to make * larger DSF sequences more readable and easier to override. * * The order of execution of the {@code @Execute} methods is determined by * the {@link #getExecutionOrder()} method. * * {@code @Execute} methods can be grouped in a hierarchical set of groups, * which should be included in the result of {@link #getExecutionOrder()}. * Using groups can make overriding slightly simpler. * * A usage example follows: <code><pre> * public class MyReflectionSequence extends ReflectionSequence { * * public MyReflectionSequence(DsfExecutor executor) { * super(executor); * } * * protected static final String GROUP_INIT = "GROUP_INIT"; * * {@code @Override} * protected String[] getExecutionOrder(String group) { * if (GROUP_TOP_LEVEL.equals(group)) { * // This is the top level group which contains * // all sub-groups, or steps that are not in * // other groups. * return new String[] { GROUP_INIT, "step3", "step4" }; * } * * // Now deal with the content of sub-groups * if (GROUP_INIT.equals(group)) { * return new String[] { "step1", "step2" }; * } * * // An invalid group was requested * return null; * } * * {@code @Execute} * public void step1(RequestMonitor rm) { * // Do something * rm.done(); * } * * {@code @RollBack("step1")} * public void rollBack1(RequestMonitor rm) { * // Rollback what was done in step1() * rm.done(); * } * * {@code @Execute} * public void step2(RequestMonitor rm) { * // Do something else * rm.done(); * } * * {@code @Execute} * public void step3(RequestMonitor rm) { * // Do something else * rm.done(); * } * * {@code @Execute} * public void step4(RequestMonitor rm) { * // Do something else * rm.done(); * } * } * </pre></code> * * @since 2.2 */ abstract public class ReflectionSequence extends Sequence { /** * The top-level group in which all sub-groups or steps that * are not part of any sub-groups are contained. This group * identifier is the one that will be used in the first call to * {@link #getExecutionOrder()}. */ public static final String GROUP_TOP_LEVEL = "GROUP_TOP_LEVEL"; //$NON-NLS-1$ private Step[] fReflectionSteps; /** * Annotation used to indicate that a method corresponds to an * {@link Sequence.Step#execute()} method of a {@link Sequence.Step}. * The annotated method must be declared public. */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public static @interface Execute {} /** * Annotation used to indicate that a method corresponds to a * {@link Sequence.Step#rollBack()} method of a {@link Sequence.Step}. * Declaring such a method is optional. If declared, the annotated * method must be declared public. */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public static @interface RollBack { /** * Name of the method tagged with the {@link Execute} annotation that this method rolls back. */ String value(); } private class ReflectionStep extends Step { final private Method fExecuteMethod; final private Method fRollbackMethod; private ReflectionStep(Method executeMethod, Method rollbackMethod) { assert executeMethod != null; fExecuteMethod = executeMethod;; fRollbackMethod = rollbackMethod; } @Override public void execute(RequestMonitor rm) { try { fExecuteMethod.invoke(ReflectionSequence.this, rm); } catch (Exception e) { rm.setStatus(new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Error executing step execute method: " + fExecuteMethod.getName(), e)); //$NON-NLS-1$ rm.done(); } } @Override public void rollBack(RequestMonitor rm) { if (fRollbackMethod == null) { super.rollBack(rm); } else { try { fRollbackMethod.invoke(ReflectionSequence.this, rm); } catch (Exception e) { rm.setStatus(new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Error executing step rollback method: " + fRollbackMethod.getName(), e)); //$NON-NLS-1$ rm.done(); } } } } public ReflectionSequence(DsfExecutor executor) { super(executor); } public ReflectionSequence(DsfExecutor executor, RequestMonitor rm) { super(executor, rm); } public ReflectionSequence(DsfExecutor executor, IProgressMonitor pm, String taskName, String rollbackTaskName) { super(executor, pm, taskName, rollbackTaskName); } public ReflectionSequence(DsfExecutor executor, RequestMonitorWithProgress rm, String taskName, String rollbackTaskName) { super(executor, rm, taskName, rollbackTaskName); } /** * This method must return the execution order of {@code @Execute} methods and/or groups. * * @param groupName The name of a group for which the list of {@code @Execute} methods * or sub-groups should be returned in the order they should be executed. * If the concept of groups is not used, then this parameter can be ignored, * at the top-level ordering should be returned. * * @return An array containing the list of @Execute methods and groups in the order * they should be executed, or null if the specified groupName is unknown. */ abstract protected String[] getExecutionOrder(String groupName); @Override public Step[] getSteps() { if (fReflectionSteps == null) { Map<String, Method> executeMethods = getAnnotatedMethods(Execute.class); Map<String, Method> rollBackMethods = getAnnotatedMethods(RollBack.class); List<Step> steps = getGroupSteps(GROUP_TOP_LEVEL, executeMethods, rollBackMethods); fReflectionSteps = steps.toArray(new ReflectionStep[steps.size()]); } return fReflectionSteps; } private List<Step> getGroupSteps(String groupId, Map<String, Method> executeMethods, Map<String, Method> rollBackMethods) { List<Step> steps = new ArrayList<Step>(executeMethods.size()); String[] order = getExecutionOrder(groupId); if (order == null) { throw new RuntimeException("Unknown group in sequence: " + groupId); //$NON-NLS-1$ } for (String name : order) { Method executeMethod = executeMethods.get(name); if (executeMethod == null) { // name is a group id steps.addAll(getGroupSteps(name, executeMethods, rollBackMethods)); } else { steps.add(new ReflectionStep(executeMethod, rollBackMethods.get(executeMethod.getName()))); } } return steps; } private Map<String, Method> getAnnotatedMethods(Class<? extends Annotation> annotationType) { Map<String, Method> retVal = new HashMap<String, Method>(); try { Method[] methods = getClass().getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(annotationType)) { Class<?>[] paramTypes = method.getParameterTypes(); if (paramTypes.length != 1) { // must have one and only param, the RequestMonitor throw new IllegalArgumentException("Method " + //$NON-NLS-1$ method.getDeclaringClass().getSimpleName() + "#" + method.getName() + //$NON-NLS-1$ " must have a single parameter"); //$NON-NLS-1$ } else { if (annotationType.equals(Execute.class)) { retVal.put(method.getName(), method); } else {// @Rollback retVal.put(method.getAnnotation(RollBack.class).value(), method); } } } } } catch(SecurityException e) { throw new IllegalArgumentException("No permission to access ReflectionSequence method"); //$NON-NLS-1$ } return retVal; } }