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 */
017 package org.apache.commons.scxml.model;
018
019 import java.util.ArrayList;
020 import java.util.Collection;
021 import java.util.HashMap;
022 import java.util.List;
023 import java.util.Map;
024 import java.util.StringTokenizer;
025
026 import org.apache.commons.logging.Log;
027 import org.apache.commons.scxml.Context;
028 import org.apache.commons.scxml.ErrorReporter;
029 import org.apache.commons.scxml.Evaluator;
030 import org.apache.commons.scxml.EventDispatcher;
031 import org.apache.commons.scxml.SCInstance;
032 import org.apache.commons.scxml.SCXMLExpressionException;
033 import org.apache.commons.scxml.SCXMLHelper;
034 import org.apache.commons.scxml.TriggerEvent;
035 import org.apache.commons.scxml.semantics.ErrorConstants;
036
037 /**
038 * The class in this SCXML object model that corresponds to the
039 * <send> SCXML element.
040 *
041 */
042 public class Send extends Action implements ExternalContent {
043
044 /**
045 * Serial version UID.
046 */
047 private static final long serialVersionUID = 1L;
048
049 /**
050 * The default targettype.
051 */
052 private static final String TARGETTYPE_SCXML = "scxml";
053
054 /**
055 * The spec mandated derived event when target cannot be reached
056 * for TARGETTYPE_SCXML.
057 */
058 private static final String EVENT_ERR_SEND_TARGETUNAVAILABLE =
059 "error.send.targetunavailable";
060
061 /**
062 * The ID of the send message.
063 */
064 private String sendid;
065
066 /**
067 * An expression returning the target location of the event.
068 */
069 private String target;
070
071 /**
072 * The type of the Event I/O Processor that the event.
073 * should be dispatched to
074 */
075 private String targettype;
076
077 /**
078 * The event is dispatched after the delay interval elapses.
079 */
080 private String delay;
081
082 /**
083 * The data containing information which may be used by the
084 * implementing platform to configure the event processor.
085 */
086 private String hints;
087
088 /**
089 * The namelist to the sent.
090 */
091 private String namelist;
092
093 /**
094 * The list of external nodes associated with this <send> element.
095 */
096 private List externalNodes;
097
098 /**
099 * 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 TransitionTarget parentTarget = getParentTransitionTarget();
274 Context ctx = scInstance.getContext(parentTarget);
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", parentTarget);
315 }
316 params.put(varName, varObj);
317 }
318 }
319 long wait = 0L;
320 if (!SCXMLHelper.isStringEmpty(delay)) {
321 Object delayValue = eval.eval(ctx, delay);
322 if (delayValue != null) {
323 String delayString = delayValue.toString();
324 wait = parseDelay(delayString, appLog);
325 }
326 }
327 String eventValue = event;
328 if (!SCXMLHelper.isStringEmpty(event)) {
329 eventValue = (String) eval.eval(ctx, event);
330 if (SCXMLHelper.isStringEmpty(eventValue)
331 && appLog.isWarnEnabled()) {
332 appLog.warn("<send>: event expression \"" + event
333 + "\" evaluated to null or empty String");
334 }
335 }
336 // Lets see if we should handle it ourselves
337 if (targettypeValue != null
338 && targettypeValue.trim().equalsIgnoreCase(TARGETTYPE_SCXML)) {
339 if (SCXMLHelper.isStringEmpty(targetValue)) {
340 // TODO: Remove both short-circuit passes in v1.0
341 if (wait == 0L) {
342 if (appLog.isDebugEnabled()) {
343 appLog.debug("<send>: Enqueued event '" + eventValue
344 + "' with no delay");
345 }
346 derivedEvents.add(new TriggerEvent(eventValue,
347 TriggerEvent.SIGNAL_EVENT, params));
348 return;
349 }
350 } else {
351 // We know of no other
352 if (appLog.isWarnEnabled()) {
353 appLog.warn("<send>: Unavailable target - "
354 + targetValue);
355 }
356 derivedEvents.add(new TriggerEvent(
357 EVENT_ERR_SEND_TARGETUNAVAILABLE,
358 TriggerEvent.ERROR_EVENT));
359 // short-circuit the EventDispatcher
360 return;
361 }
362 }
363 ctx.setLocal(getNamespacesKey(), null);
364 if (appLog.isDebugEnabled()) {
365 appLog.debug("<send>: Dispatching event '" + eventValue
366 + "' to target '" + targetValue + "' of target type '"
367 + targettypeValue + "' with suggested delay of " + wait
368 + "ms");
369 }
370 // Else, let the EventDispatcher take care of it
371 evtDispatcher.send(sendid, targetValue, targettypeValue, eventValue,
372 params, hintsValue, wait, externalNodes);
373 }
374
375 /**
376 * Parse delay.
377 *
378 * @param delayString The String value of the delay, in CSS2 format
379 * @param appLog The application log
380 * @return The parsed delay in milliseconds
381 * @throws SCXMLExpressionException If the delay cannot be parsed
382 */
383 private long parseDelay(final String delayString, final Log appLog)
384 throws SCXMLExpressionException {
385
386 long wait = 0L;
387 long multiplier = 1L;
388
389 if (!SCXMLHelper.isStringEmpty(delayString)) {
390
391 String trimDelay = delayString.trim();
392 String numericDelay = trimDelay;
393 if (trimDelay.endsWith(MILLIS)) {
394 numericDelay = trimDelay.substring(0, trimDelay.length() - 2);
395 } else if (trimDelay.endsWith(SECONDS)) {
396 multiplier = MILLIS_IN_A_SECOND;
397 numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
398 } else if (trimDelay.endsWith(MINUTES)) { // Not CSS2
399 multiplier = MILLIS_IN_A_MINUTE;
400 numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
401 }
402
403 try {
404 wait = Long.parseLong(numericDelay);
405 } catch (NumberFormatException nfe) {
406 appLog.error(nfe.getMessage(), nfe);
407 throw new SCXMLExpressionException(nfe.getMessage(), nfe);
408 }
409 wait *= multiplier;
410
411 }
412
413 return wait;
414
415 }
416
417 /** The suffix in the delay string for milliseconds. */
418 private static final String MILLIS = "ms";
419
420 /** The suffix in the delay string for seconds. */
421 private static final String SECONDS = "s";
422
423 /** The suffix in the delay string for minutes. */
424 private static final String MINUTES = "m";
425
426 /** The number of milliseconds in a second. */
427 private static final long MILLIS_IN_A_SECOND = 1000L;
428
429 /** The number of milliseconds in a minute. */
430 private static final long MILLIS_IN_A_MINUTE = 60000L;
431
432 }
433