##### Coverage Report - org.apache.commons.scxml.model.Send

Classes in this File | Line Coverage | Branch Coverage | Complexity
Send |
85% |
100% |
| 2.1578947368421053;2.158

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 |  |
      * &lt;send&gt; 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 &lt;send&gt; 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 | 15 |
             super();
118 | 15 |
             this.externalNodes = new ArrayList();
119 | 15 |
         }
120 |  |
121 |  |
         /**
122 |  |
          * Get the delay.
123 |  |
          *
124 |  |
          * @return Returns the delay.
125 |  |
          */
126 |  |
         public final String getDelay() {
127 | 1 |
             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 | 4 |
             this.delay = delay;
137 | 4 |
         }
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 | 15 |
             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 | 0 |
             this.externalNodes = externalNodes;
155 | 0 |
         }
156 |  |
157 |  |
         /**
158 |  |
          * Get the hints for this &lt;send&gt; element.
159 |  |
          *
160 |  |
          * @return String Returns the hints.
161 |  |
          */
162 |  |
         public final String getHints() {
163 | 1 |
             return hints;
164 |  |
         }
165 |  |
166 |  |
         /**
167 |  |
          * Set the hints for this &lt;send&gt; element.
168 |  |
          *
169 |  |
          * @param hints The hints to set.
170 |  |
          */
171 |  |
         public final void setHints(final String hints) {
172 | 2 |
             this.hints = hints;
173 | 2 |
         }
174 |  |
175 |  |
         /**
176 |  |
          * Get the namelist.
177 |  |
          *
178 |  |
          * @return String Returns the namelist.
179 |  |
          */
180 |  |
         public final String getNamelist() {
181 | 1 |
             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 | 5 |
             this.namelist = namelist;
191 | 5 |
         }
192 |  |
193 |  |
         /**
194 |  |
          * Get the identifier for this &lt;send&gt; element.
195 |  |
          *
196 |  |
          * @return String Returns the sendid.
197 |  |
          */
198 |  |
         public final String getSendid() {
199 | 2 |
             return sendid;
200 |  |
         }
201 |  |
202 |  |
         /**
203 |  |
          * Set the identifier for this &lt;send&gt; element.
204 |  |
          *
205 |  |
          * @param sendid The sendid to set.
206 |  |
          */
207 |  |
         public final void setSendid(final String sendid) {
208 | 4 |
             this.sendid = sendid;
209 | 4 |
         }
210 |  |
211 |  |
         /**
212 |  |
          * Get the target for this &lt;send&gt; element.
213 |  |
          *
214 |  |
          * @return String Returns the target.
215 |  |
          */
216 |  |
         public final String getTarget() {
217 | 1 |
             return target;
218 |  |
         }
219 |  |
220 |  |
         /**
221 |  |
          * Set the target for this &lt;send&gt; element.
222 |  |
          *
223 |  |
          * @param target The target to set.
224 |  |
          */
225 |  |
         public final void setTarget(final String target) {
226 | 8 |
             this.target = target;
227 | 8 |
         }
228 |  |
229 |  |
         /**
230 |  |
          * Get the target type for this &lt;send&gt; element.
231 |  |
          *
232 |  |
          * @return String Returns the targettype.
233 |  |
          */
234 |  |
         public final String getTargettype() {
235 | 1 |
             return targettype;
236 |  |
         }
237 |  |
238 |  |
         /**
239 |  |
          * Set the target type for this &lt;send&gt; element.
240 |  |
          *
241 |  |
          * @param targettype The targettype to set.
242 |  |
          */
243 |  |
         public final void setTargettype(final String targettype) {
244 | 12 |
             this.targettype = targettype;
245 | 12 |
         }
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 | 10 |
             this.event = event;
254 | 10 |
         }
255 |  |
256 |  |
         /**
257 |  |
          * Set the event to send.
258 |  |
          *
259 |  |
          * @return String Returns the event.
260 |  |
          */
261 |  |
         public final String getEvent() {
262 | 1 |
             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 | 12 |
             State parentState = getParentState();
274 | 12 |
             Context ctx = scInstance.getContext(parentState);
275 | 12 |
             ctx.setLocal(getNamespacesKey(), getNamespaces());
276 | 12 |
             Evaluator eval = scInstance.getEvaluator();
277 |  |
             // Most attributes of <send> are expressions so need to be
278 |  |
             // evaluated before the EventDispatcher callback
279 | 12 |
             Object hintsValue = null;
280 | 12 |
             if (!SCXMLHelper.isStringEmpty(hints)) {
281 | 1 |
                 hintsValue = eval.eval(ctx, hints);
282 |  |
             }
283 | 12 |
             String targetValue = target;
284 | 12 |
             if (!SCXMLHelper.isStringEmpty(target)) {
285 | 4 |
                 targetValue = (String) eval.eval(ctx, target);
286 | 4 |
                 if (SCXMLHelper.isStringEmpty(targetValue)
287 |  |
                         && appLog.isWarnEnabled()) {
288 | 1 |
                     appLog.warn("<send>: target expression \"" + target
289 |  |
                         + "\" evaluated to null or empty String");
290 |  |
                 }
291 |  |
             }
292 | 12 |
             String targettypeValue = targettype;
293 | 12 |
             if (!SCXMLHelper.isStringEmpty(targettype)) {
294 | 8 |
                 targettypeValue = (String) eval.eval(ctx, targettype);
295 | 8 |
                 if (SCXMLHelper.isStringEmpty(targettypeValue)
296 |  |
                         && appLog.isWarnEnabled()) {
297 | 0 |
                     appLog.warn("<send>: targettype expression \"" + targettype
298 |  |
                         + "\" evaluated to null or empty String");
299 |  |
                 }
300 |  |
             } else {
301 |  |
                 // must default to 'scxml' when unspecified
302 | 4 |
                 targettypeValue = TARGETTYPE_SCXML;
303 |  |
             }
304 | 12 |
             Map params = null;
305 | 12 |
             if (!SCXMLHelper.isStringEmpty(namelist)) {
306 | 3 |
                 StringTokenizer tkn = new StringTokenizer(namelist);
307 | 3 |
                 params = new HashMap(tkn.countTokens());
308 | 7 |
                 while (tkn.hasMoreTokens()) {
309 | 4 |
                     String varName = tkn.nextToken();
310 | 4 |
                     Object varObj = ctx.get(varName);
311 | 4 |
                     if (varObj == null) {
312 |  |
                         //considered as a warning here
313 | 0 |
                         errRep.onError(ErrorConstants.UNDEFINED_VARIABLE,
314 |  |
                                 varName + " = null", parentState);
315 |  |
                     }
316 | 4 |
                     params.put(varName, varObj);
317 |  |
                 }
318 |  |
             }
319 | 12 |
             long wait = parseDelay(appLog);
320 |  |
             // Lets see if we should handle it ourselves
321 | 12 |
             if (targettypeValue != null
322 |  |
                   && targettypeValue.trim().equalsIgnoreCase(TARGETTYPE_SCXML)) {
323 | 8 |
                 if (SCXMLHelper.isStringEmpty(targetValue)) {
324 |  |
                     // TODO: Remove both short-circuit passes in v1.0
325 | 7 |
                     if (wait == 0L) {
326 | 7 |
                         if (appLog.isDebugEnabled()) {
327 | 0 |
                             appLog.debug("<send>: Enqueued event '" + event
328 |  |
                                 + "' with no delay");
329 |  |
                         }
330 | 7 |
                         derivedEvents.add(new TriggerEvent(event,
331 |  |
                             TriggerEvent.SIGNAL_EVENT));
332 | 7 |
                         return;
333 |  |
                     }
334 |  |
                 } else {
335 |  |
                     // We know of no other
336 | 1 |
                     if (appLog.isWarnEnabled()) {
337 | 1 |
                         appLog.warn("<send>: Unavailable target - "
338 |  |
                             + targetValue);
339 |  |
                     }
340 | 1 |
                     derivedEvents.add(new TriggerEvent(
341 |  |
                         EVENT_ERR_SEND_TARGETUNAVAILABLE,
342 |  |
                         TriggerEvent.ERROR_EVENT));
343 |  |
                     // short-circuit the EventDispatcher
344 | 1 |
                     return;
345 |  |
                 }
346 |  |
             }
347 | 4 |
             ctx.setLocal(getNamespacesKey(), null);
348 | 4 |
             if (appLog.isDebugEnabled()) {
349 | 0 |
                 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 | 4 |
             evtDispatcher.send(sendid, targetValue, targettypeValue, event,
356 |  |
                 params, hintsValue, wait, externalNodes);
357 | 4 |
         }
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 | 12 |
             long wait = 0L;
370 | 12 |
             long multiplier = 1L;
371 |  |
372 | 12 |
             if (!SCXMLHelper.isStringEmpty(delay)) {
373 |  |
374 | 2 |
                 String trimDelay = delay.trim();
375 | 2 |
                 String numericDelay = trimDelay;
376 | 2 |
                 if (trimDelay.endsWith(MILLIS)) {
377 | 0 |
                     numericDelay = trimDelay.substring(0, trimDelay.length() - 2);
378 | 2 |
                 } else if (trimDelay.endsWith(SECONDS)) {
379 | 0 |
                     multiplier = MILLIS_IN_A_SECOND;
380 | 0 |
                     numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
381 | 2 |
                 } else if (trimDelay.endsWith(MINUTES)) {
382 | 0 |
                     multiplier = MILLIS_IN_A_MINUTE;
383 | 0 |
                     numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
384 |  |
                 }
385 |  |
386 |  |
                 try {
387 | 2 |
                     wait = Long.parseLong(numericDelay);
388 | 0 |
                 } catch (NumberFormatException nfe) {
389 | 0 |
                     appLog.error(nfe.getMessage(), nfe);
390 | 0 |
                     throw new SCXMLExpressionException(nfe.getMessage(), nfe);
391 | 2 |
                 }
392 | 2 |
                 wait *= multiplier;
393 |  |
394 |  |
             }
395 |  |
396 | 12 |
             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 |  |