001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.model;
018
019import java.util.HashMap;
020import java.util.Map;
021
022import org.apache.commons.scxml2.ActionExecutionContext;
023import org.apache.commons.scxml2.Context;
024import org.apache.commons.scxml2.Evaluator;
025import org.apache.commons.scxml2.PathResolver;
026import org.apache.commons.scxml2.SCXMLExecutionContext;
027import org.apache.commons.scxml2.SCXMLExpressionException;
028import org.apache.commons.scxml2.SCXMLSystemContext;
029import org.apache.commons.scxml2.TriggerEvent;
030import org.apache.commons.scxml2.invoke.Invoker;
031import org.apache.commons.scxml2.invoke.InvokerException;
032import org.apache.commons.scxml2.semantics.ErrorConstants;
033import org.w3c.dom.Node;
034
035/**
036 * The class in this SCXML object model that corresponds to the
037 * <invoke> SCXML element.
038 *
039 */
040public class Invoke extends NamelistHolder implements PathResolverHolder, ContentContainer {
041
042    /**
043     * Serial version UID.
044     */
045    private static final long serialVersionUID = 1L;
046
047    /**
048     * The default context variable key under which the current SCXMLExecutionContext is provided
049     */
050    private static final String CURRENT_EXECUTION_CONTEXT_KEY = "_CURRENT_EXECUTION_CONTEXT";
051
052    /**
053     * Identifier for this Invoke.
054     * */
055    private String id;
056
057    /**
058     * Path expression evaluating to a location within a previously defined XML data tree.
059     */
060    private String idlocation;
061
062    /**
063     * The type of target to be invoked.
064     */
065    private String type;
066
067    /**
068     * An expression defining the type of the target to be invoked.
069     */
070    private String typeexpr;
071
072    /**
073     * The source URL for the external service.
074     */
075    private String src;
076
077    /**
078     * The expression that evaluates to the source URL for the
079     * external service.
080     */
081    private String srcexpr;
082
083    /**
084     * A flag indicating whether to forward events to the invoked process.
085     */
086    private Boolean autoForward;
087
088    /**
089     * The <finalize> child, may be null.
090     */
091    private Finalize finalize;
092
093    /**
094     * {@link PathResolver} for resolving the "src" or "srcexpr" result.
095     */
096    private PathResolver pathResolver;
097
098    /**
099     * 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}