Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: ef5ecb481167b5aced890321159c026f07ac95b9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*******************************************************************************
 * Copyright (c) 2010 Wind River Systems and others.
 *
 * 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:
 *     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<>(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<>();
		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;
	}

}

Back to the top