View Javadoc

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 }