View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.scxml2.model;
18  
19  import java.util.HashMap;
20  import java.util.Map;
21  
22  import org.apache.commons.scxml2.ActionExecutionContext;
23  import org.apache.commons.scxml2.Context;
24  import org.apache.commons.scxml2.Evaluator;
25  import org.apache.commons.scxml2.PathResolver;
26  import org.apache.commons.scxml2.SCXMLExecutionContext;
27  import org.apache.commons.scxml2.SCXMLExpressionException;
28  import org.apache.commons.scxml2.SCXMLSystemContext;
29  import org.apache.commons.scxml2.TriggerEvent;
30  import org.apache.commons.scxml2.invoke.Invoker;
31  import org.apache.commons.scxml2.invoke.InvokerException;
32  import org.apache.commons.scxml2.semantics.ErrorConstants;
33  import org.w3c.dom.Node;
34  
35  /**
36   * The class in this SCXML object model that corresponds to the
37   * <invoke> SCXML element.
38   *
39   */
40  public class Invoke extends NamelistHolder implements PathResolverHolder, ContentContainer {
41  
42      /**
43       * Serial version UID.
44       */
45      private static final long serialVersionUID = 1L;
46  
47      /**
48       * The default context variable key under which the current SCXMLExecutionContext is provided
49       */
50      private static final String CURRENT_EXECUTION_CONTEXT_KEY = "_CURRENT_EXECUTION_CONTEXT";
51  
52      /**
53       * Identifier for this Invoke.
54       * */
55      private String id;
56  
57      /**
58       * Path expression evaluating to a location within a previously defined XML data tree.
59       */
60      private String idlocation;
61  
62      /**
63       * The type of target to be invoked.
64       */
65      private String type;
66  
67      /**
68       * An expression defining the type of the target to be invoked.
69       */
70      private String typeexpr;
71  
72      /**
73       * The source URL for the external service.
74       */
75      private String src;
76  
77      /**
78       * The expression that evaluates to the source URL for the
79       * external service.
80       */
81      private String srcexpr;
82  
83      /**
84       * A flag indicating whether to forward events to the invoked process.
85       */
86      private Boolean autoForward;
87  
88      /**
89       * The <finalize> child, may be null.
90       */
91      private Finalize finalize;
92  
93      /**
94       * {@link PathResolver} for resolving the "src" or "srcexpr" result.
95       */
96      private PathResolver pathResolver;
97  
98      /**
99       * The <content/> of this invoke
100      */
101     private Content content;
102 
103     private EnterableState parent;
104 
105     /**
106      * Get the identifier for this invoke (may be null).
107      *
108      * @return Returns the id.
109      */
110     public final String getId() {
111         return id;
112     }
113 
114     /**
115      * Set the identifier for this invoke.
116      *
117      * @param id The id to set.
118      */
119     public final void setId(final String id) {
120         this.id = id;
121     }
122 
123     /**
124      * @return the idlocation
125      */
126     public String getIdlocation() {
127         return idlocation;
128     }
129 
130     /**
131      * Set the idlocation expression
132      * @param idlocation The idlocation expression
133      */
134     public void setIdlocation(final String idlocation) {
135         this.idlocation = idlocation;
136     }
137 
138     /**
139      * Get the type for this <invoke> element.
140      *
141      * @return String Returns the type.
142      */
143     public final String getType() {
144         return type;
145     }
146 
147     /**
148      * Set the type for this <invoke> element.
149      *
150      * @param type The type to set.
151      */
152     public final void setType(final String type) {
153         this.type = type;
154     }
155 
156     /**
157      * @return The type expression
158      */
159     public String getTypeexpr() {
160         return typeexpr;
161     }
162 
163     /**
164      * Sets the type expression
165      * @param typeexpr The type expression to set
166      */
167     public void setTypeexpr(final String typeexpr) {
168         this.typeexpr = typeexpr;
169     }
170 
171     /**
172      * Get the URL for the external service.
173      *
174      * @return String The URL.
175      */
176     public final String getSrc() {
177         return src;
178     }
179 
180     /**
181      * Set the URL for the external service.
182      *
183      * @param src The source URL.
184      */
185     public final void setSrc(final String src) {
186         this.src = src;
187     }
188 
189     /**
190      * Get the expression that evaluates to the source URL for the
191      * external service.
192      *
193      * @return String The source expression.
194      */
195     public final String getSrcexpr() {
196         return srcexpr;
197     }
198 
199     /**
200      * Set the expression that evaluates to the source URL for the
201      * external service.
202      *
203      * @param srcexpr The source expression.
204      */
205     public final void setSrcexpr(final String srcexpr) {
206         this.srcexpr = srcexpr;
207     }
208 
209 
210     /**
211      * @return Returns true if all external events should be forwarded to the invoked process.
212      */
213     public final boolean isAutoForward() {
214         return autoForward != null && autoForward;
215     }
216 
217     /**
218      * @return Returns the flag indicating whether to forward events to the invoked process.
219      */
220     public final Boolean getAutoForward() {
221         return autoForward;
222     }
223 
224     /**
225      * Set the flag indicating whether to forward events to the invoked process.
226      * @param autoForward the flag
227      */
228     public final void setAutoForward(final Boolean autoForward) {
229         this.autoForward = autoForward;
230     }
231 
232     /**
233      * Get the Finalize for this Invoke.
234      *
235      * @return Finalize The Finalize for this Invoke.
236      */
237     public final Finalize getFinalize() {
238         return finalize;
239     }
240 
241     /**
242      * Set the Finalize for this Invoke.
243      *
244      * @param finalize The Finalize for this Invoke.
245      */
246     public final void setFinalize(final Finalize finalize) {
247         this.finalize = finalize;
248     }
249 
250     /**
251      * Get the {@link PathResolver}.
252      *
253      * @return Returns the pathResolver.
254      */
255     public PathResolver getPathResolver() {
256         return pathResolver;
257     }
258 
259     /**
260      * Set the {@link PathResolver}.
261      *
262      * @param pathResolver The pathResolver to set.
263      */
264     public void setPathResolver(final PathResolver pathResolver) {
265         this.pathResolver = pathResolver;
266     }
267 
268     /**
269      * Enforce identity equality only
270      * @param other other object to compare with
271      * @return this == other
272      */
273     @Override
274     public final boolean equals(final Object other) {
275         return this == other;
276     }
277 
278     /**
279      * Enforce returning identity based hascode
280      * @return {@link System#identityHashCode(Object) System.identityHashCode(this)}
281      */
282     @Override
283     public final int hashCode() {
284         return System.identityHashCode(this);
285     }
286 
287     /**
288      * Returns the content
289      *
290      * @return the content
291      */
292     public Content getContent() {
293         return content;
294     }
295 
296     /**
297      * @return The local context variable name under which the current SCXMLExecutionContext is provided to the Invoke
298      */
299     public String getCurrentSCXMLExecutionContextKey() {
300         return CURRENT_EXECUTION_CONTEXT_KEY;
301     }
302 
303     /**
304      * Sets the content
305      *
306      * @param content the content to set
307      */
308     public void setContent(final Content content) {
309         this.content = content;
310     }
311 
312     /**
313      * Get the parent EnterableState.
314      *
315      * @return Returns the parent state
316      */
317     public EnterableState getParentEnterableState() {
318         return parent;
319     }
320 
321     /**
322      * Set the parent EnterableState.
323      * @param parent The parent state to set
324      */
325     public void setParentEnterableState(final EnterableState parent) {
326         if (parent == null) {
327             throw new IllegalArgumentException("Parent parameter cannot be null");
328         }
329         this.parent = parent;
330     }
331 
332     @SuppressWarnings("unchecked")
333     @Override
334     public void execute(final ActionExecutionContext axctx) throws ModelException {
335         EnterableState parentState = getParentEnterableState();
336         Context ctx = axctx.getContext(parentState);
337         SCXMLExecutionContext exctx = (SCXMLExecutionContext)ctx.getVars().get(getCurrentSCXMLExecutionContextKey());
338         if (exctx == null) {
339             throw new ModelException("Missing current SCXMLExecutionContext instance in context under key: "+ getCurrentSCXMLExecutionContextKey());
340         }
341         try {
342             ctx.setLocal(getNamespacesKey(), getNamespaces());
343             Evaluator eval = axctx.getEvaluator();
344 
345             String typeValue = type;
346             if (typeValue == null && typeexpr != null) {
347                 typeValue = (String) getTextContentIfNodeResult(eval.eval(ctx, typeexpr));
348                 if (typeValue == null) {
349                     throw new SCXMLExpressionException("<invoke> for state "+parentState.getId() +
350                             ": type expression \"" + typeexpr + "\" evaluated to null or empty String");
351                 }
352             }
353             if (typeValue == null) {
354                 typeValue = SCXMLExecutionContext.SCXML_INVOKER_TYPE;
355             }
356             Invoker invoker = exctx.newInvoker(typeValue);
357 
358             String invokeId = getId();
359             if (invokeId == null) {
360                 invokeId = parentState.getId() + "." + ctx.get(SCXMLSystemContext.SESSIONID_KEY);
361             }
362             if (getId() == null && getIdlocation() != null) {
363                 eval.evalAssign(ctx, idlocation, invokeId, Evaluator.AssignType.REPLACE_CHILDREN, null);
364             }
365             invoker.setInvokeId(invokeId);
366 
367             String src = getSrc();
368             if (src == null && getSrcexpr() != null) {
369                 src = (String) getTextContentIfNodeResult(eval.eval(ctx, getSrcexpr()));
370             }
371             if (src != null) {
372                 PathResolver pr = getPathResolver();
373                 if (pr != null) {
374                     src = getPathResolver().resolvePath(src);
375                 }
376             }
377             Node srcNode = null;
378             if (src == null && getContent() != null) {
379                 Object contentValue;
380                 if (content.getExpr() != null) {
381                     contentValue = eval.eval(ctx, content.getExpr());
382                 } else {
383                     contentValue = content.getBody();
384                 }
385                 if (contentValue instanceof Node) {
386                     srcNode = ((Node)contentValue).cloneNode(true);
387                 }
388                 else if (contentValue != null) {
389                     src = String.valueOf(contentValue);
390                 }
391             }
392             if (src == null && srcNode == null) {
393                 throw new SCXMLExpressionException("<invoke> for state "+parentState.getId() +
394                         ": no src and no content defined");
395             }
396             Map<String, Object> payloadDataMap = new HashMap<String, Object>();
397             addNamelistDataToPayload(axctx, payloadDataMap);
398             addParamsToPayload(axctx, payloadDataMap);
399             invoker.setParentSCXMLExecutor(exctx.getSCXMLExecutor());
400             if (src != null) {
401                 invoker.invoke(src, payloadDataMap);
402             }
403             // TODO: } else { invoker.invoke(srcNode, payloadDataMap); }
404             exctx.registerInvoker(this, invoker);
405         }
406         catch (InvokerException e) {
407             axctx.getErrorReporter().onError(ErrorConstants.EXECUTION_ERROR, e.getMessage(), this);
408             axctx.getInternalIOProcessor().addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
409         }
410         catch (SCXMLExpressionException e) {
411             axctx.getInternalIOProcessor().addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
412             axctx.getErrorReporter().onError(ErrorConstants.EXPRESSION_ERROR, e.getMessage(), this);
413         }
414         finally {
415             ctx.setLocal(getNamespacesKey(), null);
416         }
417     }
418 }