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 target type.
51       */
52      private static final String TYPE_SCXML = "scxml";
53  
54      /**
55       * The spec mandated derived event when target cannot be reached
56       * for TYPE_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 type;
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      * @deprecated Use {@link #getType()} instead.
234      */
235     public final String getTargettype() {
236         return type;
237     }
238 
239     /**
240      * Set the target type for this <send> element.
241      *
242      * @param targettype The targettype to set.
243      * @deprecated Use {@link #setType(String)} instead.
244      */
245     public final void setTargettype(final String targettype) {
246         this.type = targettype;
247     }
248 
249     /**
250      * Get the type for this <send> element.
251      *
252      * @return String Returns the type.
253      */
254     public final String getType() {
255         return type;
256     }
257 
258     /**
259      * Set the type for this <send> element.
260      *
261      * @param type The type to set.
262      */
263     public final void setType(final String type) {
264         this.type = type;
265     }
266 
267     /**
268      * Get the event to send.
269      *
270      * @param event The event to set.
271      */
272     public final void setEvent(final String event) {
273         this.event = event;
274     }
275 
276     /**
277      * Set the event to send.
278      *
279      * @return String Returns the event.
280      */
281     public final String getEvent() {
282         return event;
283     }
284 
285     /**
286      * {@inheritDoc}
287      */
288     public void execute(final EventDispatcher evtDispatcher,
289             final ErrorReporter errRep, final SCInstance scInstance,
290             final Log appLog, final Collection derivedEvents)
291     throws ModelException, SCXMLExpressionException {
292         // Send attributes evaluation
293         TransitionTarget parentTarget = getParentTransitionTarget();
294         Context ctx = scInstance.getContext(parentTarget);
295         ctx.setLocal(getNamespacesKey(), getNamespaces());
296         Evaluator eval = scInstance.getEvaluator();
297         // Most attributes of <send> are expressions so need to be
298         // evaluated before the EventDispatcher callback
299         Object hintsValue = null;
300         if (!SCXMLHelper.isStringEmpty(hints)) {
301             hintsValue = eval.eval(ctx, hints);
302         }
303         String targetValue = target;
304         if (!SCXMLHelper.isStringEmpty(target)) {
305             targetValue = (String) eval.eval(ctx, target);
306             if (SCXMLHelper.isStringEmpty(targetValue)
307                     && appLog.isWarnEnabled()) {
308                 appLog.warn("<send>: target expression \"" + target
309                     + "\" evaluated to null or empty String");
310             }
311         }
312         String typeValue = type;
313         if (!SCXMLHelper.isStringEmpty(type)) {
314             typeValue = (String) eval.eval(ctx, type);
315             if (SCXMLHelper.isStringEmpty(typeValue)
316                     && appLog.isWarnEnabled()) {
317                 appLog.warn("<send>: type expression \"" + type
318                     + "\" evaluated to null or empty String");
319             }
320         } else {
321             // must default to 'scxml' when unspecified
322             typeValue = TYPE_SCXML;
323         }
324         Map params = null;
325         if (!SCXMLHelper.isStringEmpty(namelist)) {
326             StringTokenizer tkn = new StringTokenizer(namelist);
327             params = new HashMap(tkn.countTokens());
328             while (tkn.hasMoreTokens()) {
329                 String varName = tkn.nextToken();
330                 Object varObj = ctx.get(varName);
331                 if (varObj == null) {
332                     //considered as a warning here
333                     errRep.onError(ErrorConstants.UNDEFINED_VARIABLE,
334                             varName + " = null", parentTarget);
335                 }
336                 params.put(varName, varObj);
337             }
338         }
339         long wait = 0L;
340         if (!SCXMLHelper.isStringEmpty(delay)) {
341             Object delayValue = eval.eval(ctx, delay);
342             if (delayValue != null) {
343                 String delayString = delayValue.toString();
344                 wait = parseDelay(delayString, appLog);
345             }
346         }
347         String eventValue = event;
348         if (!SCXMLHelper.isStringEmpty(event)) {
349             eventValue = (String) eval.eval(ctx, event);
350             if (SCXMLHelper.isStringEmpty(eventValue)
351                     && appLog.isWarnEnabled()) {
352                 appLog.warn("<send>: event expression \"" + event
353                     + "\" evaluated to null or empty String");
354             }
355         }
356         // Lets see if we should handle it ourselves
357         if (typeValue != null
358               && typeValue.trim().equalsIgnoreCase(TYPE_SCXML)) {
359             if (SCXMLHelper.isStringEmpty(targetValue)) {
360                 // TODO: Remove both short-circuit passes in v1.0
361                 if (wait == 0L) {
362                     if (appLog.isDebugEnabled()) {
363                         appLog.debug("<send>: Enqueued event '" + eventValue
364                             + "' with no delay");
365                     }
366                     derivedEvents.add(new TriggerEvent(eventValue,
367                         TriggerEvent.SIGNAL_EVENT, params));
368                     return;
369                 }
370             } else {
371                 // We know of no other
372                 if (appLog.isWarnEnabled()) {
373                     appLog.warn("<send>: Unavailable target - "
374                         + targetValue);
375                 }
376                 derivedEvents.add(new TriggerEvent(
377                     EVENT_ERR_SEND_TARGETUNAVAILABLE,
378                     TriggerEvent.ERROR_EVENT));
379                 // short-circuit the EventDispatcher
380                 return;
381             }
382         }
383         ctx.setLocal(getNamespacesKey(), null);
384         if (appLog.isDebugEnabled()) {
385             appLog.debug("<send>: Dispatching event '" + eventValue
386                 + "' to target '" + targetValue + "' of target type '"
387                 + typeValue + "' with suggested delay of " + wait
388                 + "ms");
389         }
390         // Else, let the EventDispatcher take care of it
391         evtDispatcher.send(sendid, targetValue, typeValue, eventValue,
392             params, hintsValue, wait, externalNodes);
393     }
394 
395     /**
396      * Parse delay.
397      *
398      * @param delayString The String value of the delay, in CSS2 format
399      * @param appLog The application log
400      * @return The parsed delay in milliseconds
401      * @throws SCXMLExpressionException If the delay cannot be parsed
402      */
403     private long parseDelay(final String delayString, final Log appLog)
404     throws SCXMLExpressionException {
405 
406         long wait = 0L;
407         long multiplier = 1L;
408 
409         if (!SCXMLHelper.isStringEmpty(delayString)) {
410 
411             String trimDelay = delayString.trim();
412             String numericDelay = trimDelay;
413             if (trimDelay.endsWith(MILLIS)) {
414                 numericDelay = trimDelay.substring(0, trimDelay.length() - 2);
415             } else if (trimDelay.endsWith(SECONDS)) {
416                 multiplier = MILLIS_IN_A_SECOND;
417                 numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
418             } else if (trimDelay.endsWith(MINUTES)) { // Not CSS2
419                 multiplier = MILLIS_IN_A_MINUTE;
420                 numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
421             }
422 
423             try {
424                 wait = Long.parseLong(numericDelay);
425             } catch (NumberFormatException nfe) {
426                 appLog.error(nfe.getMessage(), nfe);
427                 throw new SCXMLExpressionException(nfe.getMessage(), nfe);
428             }
429             wait *= multiplier;
430 
431         }
432 
433         return wait;
434 
435     }
436 
437     /** The suffix in the delay string for milliseconds. */
438     private static final String MILLIS = "ms";
439 
440     /** The suffix in the delay string for seconds. */
441     private static final String SECONDS = "s";
442 
443     /** The suffix in the delay string for minutes. */
444     private static final String MINUTES = "m";
445 
446     /** The number of milliseconds in a second. */
447     private static final long MILLIS_IN_A_SECOND = 1000L;
448 
449     /** The number of milliseconds in a minute. */
450     private static final long MILLIS_IN_A_MINUTE = 60000L;
451 
452 }