1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.di.impl;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import net.sf.jguiraffe.di.BeanProvider;
28 import net.sf.jguiraffe.di.BeanStore;
29 import net.sf.jguiraffe.di.Dependency;
30 import net.sf.jguiraffe.di.DependencyProvider;
31 import net.sf.jguiraffe.di.InjectionException;
32
33 /**
34 * <p>
35 * A special implementation of the {@code Invokable} interface that
36 * allows aggregating an arbitrary number of {@code Invokable} objects to
37 * a kind of script.
38 * </p>
39 * <p>
40 * The idea behind this class is that other objects implementing the
41 * {@code Invokable} interface can be added. They can then be executed en
42 * bloc. In addition to that a rudimentary support for variables is available:
43 * the result of an invocation can be assigned to a named variable; later this
44 * variable can be accessed again using a special
45 * {@link Dependency}, which can be created by calling the
46 * {@code getChainDependency()} method.
47 * </p>
48 * <p>
49 * The typical life-cycle of an instance of this class is as follows:
50 * <ol>
51 * <li>An instance is created using the default constructor.</li>
52 * <li>The {@code Invokable} objects to be executed are added using the
53 * {@code addInvokable()} methods. Note that the order of these calls is
54 * important; the added objects are invoked in exactly the same order.</li>
55 * <li>After adding all {@code Invokable} objects the
56 * {@code invoke()} method can be called. It triggers all contained
57 * objects.</li>
58 * </ol>
59 * By making use of different invocation implementations, indeed a kind of
60 * scripting can be achieved. For instance new objects can be created,
61 * initialized (by invoking methods on them or setting properties), and assigned
62 * to other objects. This is quite powerful, but can also become complex and
63 * hard to debug.
64 * </p>
65 * <p>
66 * This class provides some methods for accessing and manipulating local
67 * variables. So it is possible to list all currently existing variables, query
68 * their values, and even modify them. However these features are mainly
69 * intended for debugging purposes rather than for implementing additional
70 * scripting logic.
71 * </p>
72 * <p>
73 * Note: This class is not thread-safe. It is intended to be created and
74 * initialized by a single thread and then be passed to a complex bean provider
75 * with initialization support. After that no more invocations should be added.
76 * Because the local variables of a currently executed script are internally
77 * stored, it is especially not possible to have multiple concurrent
78 * invocations.
79 * </p>
80 *
81 * @author Oliver Heger
82 * @version $Id: ChainedInvocation.java 207 2012-02-09 07:30:13Z oheger $
83 */
84 public class ChainedInvocation implements Invokable
85 {
86 /** Stores a map with the local variables. */
87 private final Map<String, Object> variables;
88
89 /** A cache for the so far created chain dependencies. */
90 private final Map<String, ChainDependency> chainDependencies;
91
92 /** Stores a list with the contained {@code Invokable} objects. */
93 private final List<ChainInvocationData> invokables;
94
95 /** The name of the result variable. */
96 private String resultVariableName;
97
98 /** A flag whether the variables are to be cleared before an invocation. */
99 private boolean clearVariables;
100
101 /**
102 * Creates a new instance of {@code ChainedInvocation}.
103 */
104 public ChainedInvocation()
105 {
106 variables = new HashMap<String, Object>();
107 chainDependencies = new HashMap<String, ChainDependency>();
108 invokables = new LinkedList<ChainInvocationData>();
109 clearVariables = true;
110 }
111
112 /**
113 * Adds the specified {@code Invokable} object to this object. It
114 * will become part of the invocation sequence.
115 *
116 * @param inv the object to be added (must not be <b>null</b>)
117 * @throws IllegalArgumentException if the passed in {@code Invokable}
118 * object is <b>null</b>
119 */
120 public void addInvokable(Invokable inv)
121 {
122 addInvokable(inv, null, null);
123 }
124
125 /**
126 * Adds the specified {@code Invokable} object to this object and
127 * initializes its result variable. It will become part of the invocation
128 * sequence. The result of its invocation will be stored in a local variable
129 * with the given name.
130 *
131 * @param inv the object to be added (must not be <b>null</b>)
132 * @param result the name of the result variable
133 * @throws IllegalArgumentException if the passed in {@code Invokable}
134 * object is <b>null</b>
135 */
136 public void addInvokable(Invokable inv, String result)
137 {
138 addInvokable(inv, result, null);
139 }
140
141 /**
142 * Adds the specified {@code Invokable} object to this object and
143 * initializes its result variable and its source object. It will become
144 * part of the invocation sequence. The result of its invocation will be
145 * stored in a local variable with the given name. The invocation is not
146 * performed on the current target object, but on the object stored in the
147 * local variable with the given source name. This makes it possible to
148 * manipulate other objects than the current main target.
149 *
150 * @param inv the object to be added (must not be <b>null</b>)
151 * @param result the name of the result variable
152 * @param source the name of the variable, which contains the target object
153 * for this invocation
154 * @throws IllegalArgumentException if the passed in {@code Invokable}
155 * object is <b>null</b>
156 */
157 public void addInvokable(Invokable inv, String result, String source)
158 {
159 if (inv == null)
160 {
161 throw new IllegalArgumentException("Invokable must not be null!");
162 }
163 invokables.add(new ChainInvocationData(inv, result, source));
164 }
165
166 /**
167 * Returns a list with the {@code Invokable} objects that have
168 * already been added to this chain. Manipulations of this list do not
169 * affect this object. It is empty if nothing has been added yet.
170 *
171 * @return a list with the children of this chain
172 */
173 public List<Invokable> getInvokables()
174 {
175 List<Invokable> result = new ArrayList<Invokable>(invokables.size());
176 for (ChainInvocationData cid : invokables)
177 {
178 result.add(cid.getInvokable());
179 }
180 return result;
181 }
182
183 /**
184 * Returns the number of {@code Invokable} objects contained in this
185 * chain.
186 *
187 * @return the size of this chain
188 */
189 public int size()
190 {
191 return invokables.size();
192 }
193
194 /**
195 * Returns a special {@code Dependency} for a local variable that is
196 * used during the execution of a {@code ChainedInvocation}. Local
197 * variables are created by specifying result names for
198 * {@code Invokable}s when they are added to the chain: the result
199 * of this invocation will then be stored in a variable with this name. If
200 * this variable later needs to be used for another invocation (e.g. as the
201 * parameter of a method call), it can be accessed using such a dependency.
202 *
203 * @param name the name of the local variable to be accessed (must not be
204 * <b>null</b>)
205 * @return the dependency for accessing the specified local variable
206 * @throws IllegalArgumentException if the name is <b>null</b>
207 */
208 public Dependency getChainDependency(String name)
209 {
210 return fetchVariableDependency(name);
211 }
212
213 /**
214 * Returns a {@code BeanProvider} for the local variable with the given
215 * name. Using this method, variables created during script execution can be
216 * accessed as beans and thus can act as providers for other tags.
217 *
218 * @param name the name of the local variable to be accessed (must not be
219 * <b>null</b>)
220 * @return a {@code BeanProvider} wrapping this local variable
221 * @throws IllegalArgumentException if the name is <b>null</b>
222 * @since 1.1
223 */
224 public BeanProvider getVariableBean(String name)
225 {
226 return fetchVariableDependency(name);
227 }
228
229 /**
230 * Returns a set with the names of the currently defined local variables.
231 * These names can be passed to the {@code getVariable()} method for
232 * querying the current values of these variables.
233 *
234 * @return a set with the names of the currently existing local variables
235 */
236 public Set<String> getVariableNames()
237 {
238 return variables.keySet();
239 }
240
241 /**
242 * Returns the name of the variable with the given name. If there is no
243 * variable with this name, an exception is thrown. (We consider an access
244 * to an undefined variable an error in the invocation chain.)
245 *
246 * @param name the name of the variable to be queried
247 * @return the value of this variable
248 * @throws InjectionException if the variable cannot be found
249 */
250 public Object getVariable(String name)
251 {
252 Object result = variables.get(name);
253 if (result == null)
254 {
255 throw new InjectionException("Variable cannot be resolved: " + name);
256 }
257 return result;
258 }
259
260 /**
261 * Sets the value of the specified variable. If the variable does not exist
262 * yet, it is created. A value of <b>null</b> removes the variable.
263 *
264 * @param name the name of the variable (must not be <b>null</b>)
265 * @param value the new value of the variable
266 * @throws IllegalArgumentException if the name is <b>null</b>
267 */
268 public void setVariable(String name, Object value)
269 {
270 variables.put(name, value);
271 }
272
273 /**
274 * Returns a flag whether all local variables are to be cleared for each new
275 * invocation.
276 *
277 * @return the clear variables flag
278 */
279 public boolean isClearVariables()
280 {
281 return clearVariables;
282 }
283
284 /**
285 * Sets the value of the {@code clear variables} flag. If this flag
286 * is set to <b>true</b> (which is the default value), the storage for
287 * local variables is cleared at the beginning of an invocation. This
288 * ensures that values generated by earlier invocations do not affect the
289 * current invocation. If variables have been set manually using the
290 * {@code setVariable()} method, it will be necessary to disable this
291 * flag; otherwise these variables will also get lost.
292 *
293 * @param clearVariables the new value of the flag
294 */
295 public void setClearVariables(boolean clearVariables)
296 {
297 this.clearVariables = clearVariables;
298 }
299
300 /**
301 * Returns the name of the result variable.
302 *
303 * @return the name of the result variable
304 * @since 1.1
305 */
306 public String getResultVariableName()
307 {
308 return resultVariableName;
309 }
310
311 /**
312 * Sets the name of the result variable. If this property is set, the
313 * {@code invoke()} method will not return the passed in target object, but
314 * the object referenced by this variable. This is useful if the script
315 * generates a result.
316 *
317 * @param resultVariableName the name of the result variable
318 * @since 1.1
319 */
320 public void setResultVariableName(String resultVariableName)
321 {
322 this.resultVariableName = resultVariableName;
323 }
324
325 /**
326 * Returns a list of the dependencies required for this invocation. This
327 * implementation creates a union of the dependencies of all contained
328 * {@code Invokable} objects.
329 *
330 * @return a list with the dependencies
331 */
332 public List<Dependency> getParameterDependencies()
333 {
334 Set<Dependency> unionDeps = new HashSet<Dependency>();
335 for (ChainInvocationData cid : invokables)
336 {
337 unionDeps.addAll(cid.getInvokable().getParameterDependencies());
338 }
339 return new ArrayList<Dependency>(unionDeps);
340 }
341
342 /**
343 * Performs the invocation represented by this class. This implementation
344 * will invoke all contained {@code Invokable} objects.
345 *
346 * @param depProvider the dependency provider
347 * @param target the target object
348 * @return the result of the invocation
349 * @throws InjectionException if an error occurs
350 * @throws IllegalArgumentException if the dependency provider is <b>null</b>
351 * or a required target object is undefined
352 */
353 public Object invoke(DependencyProvider depProvider, Object target)
354 {
355 if (isClearVariables())
356 {
357 variables.clear();
358 }
359
360 for (ChainInvocationData cid : invokables)
361 {
362 cid.performInvocation(depProvider, target);
363 }
364 return fetchScriptResult(target);
365 }
366
367 /**
368 * Returns a string representation for this object. This implementation
369 * outputs all contained {@code Invokable} objects.
370 *
371 * @return a string for this object
372 */
373 @Override
374 public String toString()
375 {
376 final char cr = '\n';
377 StringBuilder buf = new StringBuilder(getClass().getName());
378 buf.append('@').append(System.identityHashCode(this));
379 buf.append('[').append(cr);
380
381 for (ChainInvocationData cid : invokables)
382 {
383 if (cid.resultName != null)
384 {
385 buf.append("(result=").append(cid.resultName).append(')');
386 }
387 if (cid.targetName != null)
388 {
389 buf.append("(source=").append(cid.targetName).append(')');
390 }
391 buf.append(cid.getInvokable()).append(cr);
392 }
393
394 return buf.toString();
395 }
396
397 /**
398 * Returns the {@code ChainDependency} object for the specified variable.
399 *
400 * @param name the name of the variable (must not be <b>null</b>)
401 * @return the corresponding {@code ChainDependency} object
402 * @throws IllegalArgumentException if the variable name is <b>null</b>
403 */
404 private ChainDependency fetchVariableDependency(String name)
405 {
406 if (name == null)
407 {
408 throw new IllegalArgumentException(
409 "Variable name must not be null!");
410 }
411
412 ChainDependency cd = chainDependencies.get(name);
413 if (cd == null)
414 {
415 cd = new ChainDependency(name);
416 chainDependencies.put(name, cd);
417 }
418 return cd;
419 }
420
421 /**
422 * Determines the result of a script execution. This is per default the
423 * target object. If a result variable name has been specified, the value of
424 * this variable is returned.
425 *
426 * @param target the target of the script execution
427 * @return the result of the script execution
428 * @throws InjectionException if the variable cannot be found
429 */
430 private Object fetchScriptResult(Object target)
431 {
432 return (getResultVariableName() != null) ? getVariable(getResultVariableName())
433 : target;
434 }
435
436 /**
437 * A data class for storing the contained invocation objects. Instances of
438 * this class store all information required for executing an invocation.
439 */
440 private class ChainInvocationData
441 {
442 /** Stores the invokable object. */
443 private Invokable invokable;
444
445 /** Stores the name of the result variable if any. */
446 private String resultName;
447
448 /** Stores the name of the target variable for the invocation. */
449 private String targetName;
450
451 /**
452 * Creates a new instance of {@code ChainInvocationData}.
453 *
454 * @param inv the wrapped {@code Invokable} object
455 * @param result the name of the result variable
456 * @param target the name of the target variable
457 */
458 public ChainInvocationData(Invokable inv, String result, String target)
459 {
460 invokable = inv;
461 resultName = result;
462 targetName = target;
463 }
464
465 /**
466 * Returns the managed {@code Invokable} object.
467 *
468 * @return the internal {@code Invokable} object
469 */
470 public Invokable getInvokable()
471 {
472 return invokable;
473 }
474
475 /**
476 * Performs the invocation. Calls the internally stored invocation
477 * object. If a target name is set, this variable is resolved, and the
478 * invocation is executed on this object; otherwise then provided target
479 * object is used. If a result name is set, the result of the invocation
480 * is stored in this variable.
481 *
482 * @param depProvider the dependency provider
483 * @param target the default target object for this invocation
484 * @throws InjectionException if the invocation causes an error
485 */
486 public void performInvocation(DependencyProvider depProvider,
487 Object target)
488 {
489 Object t = (targetName != null) ? getVariable(targetName) : target;
490 Object res = getInvokable().invoke(depProvider, t);
491 if (resultName != null)
492 {
493 setVariable(resultName, res);
494 }
495 }
496 }
497
498 /**
499 * An internally used {@code Dependency} implementation for accessing
500 * local variables. Instances of this class simply store the name of the
501 * desired local variable. The getBean() method is implemented by querying
502 * the map with the local variables.
503 */
504 private class ChainDependency implements Dependency, BeanProvider
505 {
506 /** Stores the name of the desired variable. */
507 private String varName;
508
509 /**
510 * Creates a new instance of {@code ChainDependency} for the
511 * specified variable.
512 *
513 * @param var the name of the variable
514 */
515 public ChainDependency(String var)
516 {
517 varName = var;
518 }
519
520 /**
521 * Resolves this dependency. This implementation ignores all parameters
522 * and simply returns a pointer to itself. So the
523 * {@code BeanProvider} implementation is used for querying the
524 * managed bean.
525 *
526 * @param store the bean store
527 * @param depProvider the dependency provider
528 * @return the bean provider this dependency refers to
529 */
530 public BeanProvider resolve(BeanStore store,
531 DependencyProvider depProvider)
532 {
533 return this;
534 }
535
536 /**
537 * Returns the bean managed by this provider. This implementation
538 * queries the local variable with the specified name.
539 *
540 * @param dependencyProvider the dependency provider (ignored)
541 * @return the managed bean
542 */
543 public Object getBean(DependencyProvider dependencyProvider)
544 {
545 return getVariable(varName);
546 }
547
548 /**
549 * Returns the class of the managed bean. This implementation evaluates
550 * the managed object and obtains its class.
551 *
552 * @param dependencyProvider the dependency provider (ignored)
553 * @return the class of the managed bean
554 */
555 public Class<?> getBeanClass(DependencyProvider dependencyProvider)
556 {
557 return getBean(dependencyProvider).getClass();
558 }
559
560 /**
561 * Returns the dependencies of this bean provider. This implementation
562 * always returns an empty set because there are no dependencies.
563 *
564 * @return a set with the dependencies
565 */
566 public Set<Dependency> getDependencies()
567 {
568 return Collections.emptySet();
569 }
570
571 /**
572 * Returns the ID of the locking transaction. This implementation always
573 * returns <b>null</b> because there is no life-cycle support.
574 *
575 * @return the ID of the locking transaction
576 */
577 public Long getLockID()
578 {
579 return null;
580 }
581
582 /**
583 * Sets the ID of the locking transaction. This is just a dummy.
584 *
585 * @param lid the locking ID
586 */
587 public void setLockID(Long lid)
588 {
589 }
590
591 /**
592 * Returns a flag whether the managed bean is available. This is always
593 * the case.
594 *
595 * @return a flag whether the managed bean can be accessed
596 */
597 public boolean isBeanAvailable()
598 {
599 return true;
600 }
601
602 /**
603 * Notifies this bean provider that it is no more needed. This is just
604 * an empty dummy implementation.
605 *
606 * @param depProvider the {@code DependencyProvider}
607 */
608 public void shutdown(DependencyProvider depProvider)
609 {
610 }
611 }
612 }