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.scxml.model;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.StringTokenizer;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.scxml.Context;
28  import org.apache.commons.scxml.ErrorReporter;
29  import org.apache.commons.scxml.Evaluator;
30  import org.apache.commons.scxml.EventDispatcher;
31  import org.apache.commons.scxml.SCInstance;
32  import org.apache.commons.scxml.SCXMLExpressionException;
33  import org.apache.commons.scxml.SCXMLHelper;
34  import org.apache.commons.scxml.TriggerEvent;
35  import org.apache.commons.scxml.semantics.ErrorConstants;
36  
37  /***
38   * The class in this SCXML object model that corresponds to the
39   * <send> SCXML element.
40   *
41   */
42  public class Send extends Action implements ExternalContent {
43  
44      /***
45       * Serial version UID.
46       */
47      private static final long serialVersionUID = 1L;
48  
49      /***
50       * The default targettype.
51       */
52      private static final String TARGETTYPE_SCXML = "scxml";
53  
54      /***
55       * The spec mandated derived event when target cannot be reached
56       * for TARGETTYPE_SCXML.
57       */
58      private static final String EVENT_ERR_SEND_TARGETUNAVAILABLE =
59          "error.send.targetunavailable";
60  
61      /***
62       * The ID of the send message.
63       */
64      private String sendid;
65  
66      /***
67       * An expression returning the target location of the event.
68       */
69      private String target;
70  
71      /***
72       * The type of the Event I/O Processor that the event.
73       * should be dispatched to
74       */
75      private String targettype;
76  
77      /***
78       * The event is dispatched after the delay interval elapses.
79       */
80      private String delay;
81  
82      /***
83       * The data containing information which may be used by the
84       * implementing platform to configure the event processor.
85       */
86      private String hints;
87  
88      /***
89       * The namelist to the sent.
90       */
91      private String namelist;
92  
93      /***
94       * The list of external nodes associated with this <send> element.
95       */
96      private List externalNodes;
97  
98      /***
99       * The type of event being generated.
100      */
101     private String event;
102 
103     /***
104      * OutputFormat used to serialize external nodes.
105      *
106     private static final OutputFormat format;
107     static {
108         format = new OutputFormat();
109         format.setOmitXMLDeclaration(true);
110     }
111     */
112 
113     /***
114      * Constructor.
115      */
116     public Send() {
117         super();
118         this.externalNodes = new ArrayList();
119     }
120 
121     /***
122      * Get the delay.
123      *
124      * @return Returns the delay.
125      */
126     public final String getDelay() {
127         return delay;
128     }
129 
130     /***
131      * Set the delay.
132      *
133      * @param delay The delay to set.
134      */
135     public final void setDelay(final String delay) {
136         this.delay = delay;
137     }
138 
139     /***
140      * Get the list of external namespaced child nodes.
141      *
142      * @return List Returns the list of externalnodes.
143      */
144     public final List getExternalNodes() {
145         return externalNodes;
146     }
147 
148     /***
149      * Set the list of external namespaced child nodes.
150      *
151      * @param externalNodes The externalnode to set.
152      */
153     public final void setExternalNodes(final List externalNodes) {
154         this.externalNodes = externalNodes;
155     }
156 
157     /***
158      * Get the hints for this <send> element.
159      *
160      * @return String Returns the hints.
161      */
162     public final String getHints() {
163         return hints;
164     }
165 
166     /***
167      * Set the hints for this <send> element.
168      *
169      * @param hints The hints to set.
170      */
171     public final void setHints(final String hints) {
172         this.hints = hints;
173     }
174 
175     /***
176      * Get the namelist.
177      *
178      * @return String Returns the namelist.
179      */
180     public final String getNamelist() {
181         return namelist;
182     }
183 
184     /***
185      * Set the namelist.
186      *
187      * @param namelist The namelist to set.
188      */
189     public final void setNamelist(final String namelist) {
190         this.namelist = namelist;
191     }
192 
193     /***
194      * Get the identifier for this <send> element.
195      *
196      * @return String Returns the sendid.
197      */
198     public final String getSendid() {
199         return sendid;
200     }
201 
202     /***
203      * Set the identifier for this <send> element.
204      *
205      * @param sendid The sendid to set.
206      */
207     public final void setSendid(final String sendid) {
208         this.sendid = sendid;
209     }
210 
211     /***
212      * Get the target for this <send> element.
213      *
214      * @return String Returns the target.
215      */
216     public final String getTarget() {
217         return target;
218     }
219 
220     /***
221      * Set the target for this <send> element.
222      *
223      * @param target The target to set.
224      */
225     public final void setTarget(final String target) {
226         this.target = target;
227     }
228 
229     /***
230      * Get the target type for this <send> element.
231      *
232      * @return String Returns the targettype.
233      */
234     public final String getTargettype() {
235         return targettype;
236     }
237 
238     /***
239      * Set the target type for this <send> element.
240      *
241      * @param targettype The targettype to set.
242      */
243     public final void setTargettype(final String targettype) {
244         this.targettype = targettype;
245     }
246 
247     /***
248      * Get the event to send.
249      *
250      * @param event The event to set.
251      */
252     public final void setEvent(final String event) {
253         this.event = event;
254     }
255 
256     /***
257      * Set the event to send.
258      *
259      * @return String Returns the event.
260      */
261     public final String getEvent() {
262         return event;
263     }
264 
265     /***
266      * {@inheritDoc}
267      */
268     public void execute(final EventDispatcher evtDispatcher,
269             final ErrorReporter errRep, final SCInstance scInstance,
270             final Log appLog, final Collection derivedEvents)
271     throws ModelException, SCXMLExpressionException {
272         // Send attributes evaluation
273         State parentState = getParentState();
274         Context ctx = scInstance.getContext(parentState);
275         ctx.setLocal(getNamespacesKey(), getNamespaces());
276         Evaluator eval = scInstance.getEvaluator();
277         // Most attributes of <send> are expressions so need to be
278         // evaluated before the EventDispatcher callback
279         Object hintsValue = null;
280         if (!SCXMLHelper.isStringEmpty(hints)) {
281             hintsValue = eval.eval(ctx, hints);
282         }
283         String targetValue = target;
284         if (!SCXMLHelper.isStringEmpty(target)) {
285             targetValue = (String) eval.eval(ctx, target);
286             if (SCXMLHelper.isStringEmpty(targetValue)
287                     && appLog.isWarnEnabled()) {
288                 appLog.warn("<send>: target expression \"" + target
289                     + "\" evaluated to null or empty String");
290             }
291         }
292         String targettypeValue = targettype;
293         if (!SCXMLHelper.isStringEmpty(targettype)) {
294             targettypeValue = (String) eval.eval(ctx, targettype);
295             if (SCXMLHelper.isStringEmpty(targettypeValue)
296                     && appLog.isWarnEnabled()) {
297                 appLog.warn("<send>: targettype expression \"" + targettype
298                     + "\" evaluated to null or empty String");
299             }
300         } else {
301             // must default to 'scxml' when unspecified
302             targettypeValue = TARGETTYPE_SCXML;
303         }
304         Map params = null;
305         if (!SCXMLHelper.isStringEmpty(namelist)) {
306             StringTokenizer tkn = new StringTokenizer(namelist);
307             params = new HashMap(tkn.countTokens());
308             while (tkn.hasMoreTokens()) {
309                 String varName = tkn.nextToken();
310                 Object varObj = ctx.get(varName);
311                 if (varObj == null) {
312                     //considered as a warning here
313                     errRep.onError(ErrorConstants.UNDEFINED_VARIABLE,
314                             varName + " = null", parentState);
315                 }
316                 params.put(varName, varObj);
317             }
318         }
319         long wait = parseDelay(appLog);
320         // Lets see if we should handle it ourselves
321         if (targettypeValue != null
322               && targettypeValue.trim().equalsIgnoreCase(TARGETTYPE_SCXML)) {
323             if (SCXMLHelper.isStringEmpty(targetValue)) {
324                 // TODO: Remove both short-circuit passes in v1.0
325                 if (wait == 0L) {
326                     if (appLog.isDebugEnabled()) {
327                         appLog.debug("<send>: Enqueued event '" + event
328                             + "' with no delay");
329                     }
330                     derivedEvents.add(new TriggerEvent(event,
331                         TriggerEvent.SIGNAL_EVENT));
332                     return;
333                 }
334             } else {
335                 // We know of no other
336                 if (appLog.isWarnEnabled()) {
337                     appLog.warn("<send>: Unavailable target - "
338                         + targetValue);
339                 }
340                 derivedEvents.add(new TriggerEvent(
341                     EVENT_ERR_SEND_TARGETUNAVAILABLE,
342                     TriggerEvent.ERROR_EVENT));
343                 // short-circuit the EventDispatcher
344                 return;
345             }
346         }
347         ctx.setLocal(getNamespacesKey(), null);
348         if (appLog.isDebugEnabled()) {
349             appLog.debug("<send>: Dispatching event '" + event
350                 + "' to target '" + targetValue + "' of target type '"
351                 + targettypeValue + "' with suggested delay of " + wait
352                 + "ms");
353         }
354         // Else, let the EventDispatcher take care of it
355         evtDispatcher.send(sendid, targetValue, targettypeValue, event,
356             params, hintsValue, wait, externalNodes);
357     }
358 
359     /***
360      * Parse delay.
361      *
362      * @param appLog The application log
363      * @return The parsed delay in milliseconds
364      * @throws SCXMLExpressionException If the delay cannot be parsed
365      */
366     private long parseDelay(final Log appLog)
367     throws SCXMLExpressionException {
368 
369         long wait = 0L;
370         long multiplier = 1L;
371 
372         if (!SCXMLHelper.isStringEmpty(delay)) {
373 
374             String trimDelay = delay.trim();
375             String numericDelay = trimDelay;
376             if (trimDelay.endsWith(MILLIS)) {
377                 numericDelay = trimDelay.substring(0, trimDelay.length() - 2);
378             } else if (trimDelay.endsWith(SECONDS)) {
379                 multiplier = MILLIS_IN_A_SECOND;
380                 numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
381             } else if (trimDelay.endsWith(MINUTES)) {
382                 multiplier = MILLIS_IN_A_MINUTE;
383                 numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
384             }
385 
386             try {
387                 wait = Long.parseLong(numericDelay);
388             } catch (NumberFormatException nfe) {
389                 appLog.error(nfe.getMessage(), nfe);
390                 throw new SCXMLExpressionException(nfe.getMessage(), nfe);
391             }
392             wait *= multiplier;
393 
394         }
395 
396         return wait;
397 
398     }
399 
400     /*** The suffix in the delay string for milliseconds. */
401     private static final String MILLIS = "ms";
402 
403     /*** The suffix in the delay string for seconds. */
404     private static final String SECONDS = "s";
405 
406     /*** The suffix in the delay string for minutes. */
407     private static final String MINUTES = "m";
408 
409     /*** The number of milliseconds in a second. */
410     private static final long MILLIS_IN_A_SECOND = 1000L;
411 
412     /*** The number of milliseconds in a minute. */
413     private static final long MILLIS_IN_A_MINUTE = 60000L;
414 
415 }
416