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 = "error.send.targetunavailable";
059:
060: /**
061: * The ID of the send message.
062: */
063: private String sendid;
064:
065: /**
066: * An expression returning the target location of the event.
067: */
068: private String target;
069:
070: /**
071: * The type of the Event I/O Processor that the event.
072: * should be dispatched to
073: */
074: private String targettype;
075:
076: /**
077: * The event is dispatched after the delay interval elapses.
078: */
079: private String delay;
080:
081: /**
082: * The data containing information which may be used by the
083: * implementing platform to configure the event processor.
084: */
085: private String hints;
086:
087: /**
088: * The namelist to the sent.
089: */
090: private String namelist;
091:
092: /**
093: * The list of external nodes associated with this <send> element.
094: */
095: private List externalNodes;
096:
097: /**
098: * The type of event being generated.
099: */
100: private String event;
101:
102: /**
103: * OutputFormat used to serialize external nodes.
104: *
105: private static final OutputFormat format;
106: static {
107: format = new OutputFormat();
108: format.setOmitXMLDeclaration(true);
109: }
110: */
111:
112: /**
113: * Constructor.
114: */
115: public Send() {
116: super ();
117: this .externalNodes = new ArrayList();
118: }
119:
120: /**
121: * Get the delay.
122: *
123: * @return Returns the delay.
124: */
125: public final String getDelay() {
126: return delay;
127: }
128:
129: /**
130: * Set the delay.
131: *
132: * @param delay The delay to set.
133: */
134: public final void setDelay(final String delay) {
135: this .delay = delay;
136: }
137:
138: /**
139: * Get the list of external namespaced child nodes.
140: *
141: * @return List Returns the list of externalnodes.
142: */
143: public final List getExternalNodes() {
144: return externalNodes;
145: }
146:
147: /**
148: * Set the list of external namespaced child nodes.
149: *
150: * @param externalNodes The externalnode to set.
151: */
152: public final void setExternalNodes(final List externalNodes) {
153: this .externalNodes = externalNodes;
154: }
155:
156: /**
157: * Get the hints for this <send> element.
158: *
159: * @return String Returns the hints.
160: */
161: public final String getHints() {
162: return hints;
163: }
164:
165: /**
166: * Set the hints for this <send> element.
167: *
168: * @param hints The hints to set.
169: */
170: public final void setHints(final String hints) {
171: this .hints = hints;
172: }
173:
174: /**
175: * Get the namelist.
176: *
177: * @return String Returns the namelist.
178: */
179: public final String getNamelist() {
180: return namelist;
181: }
182:
183: /**
184: * Set the namelist.
185: *
186: * @param namelist The namelist to set.
187: */
188: public final void setNamelist(final String namelist) {
189: this .namelist = namelist;
190: }
191:
192: /**
193: * Get the identifier for this <send> element.
194: *
195: * @return String Returns the sendid.
196: */
197: public final String getSendid() {
198: return sendid;
199: }
200:
201: /**
202: * Set the identifier for this <send> element.
203: *
204: * @param sendid The sendid to set.
205: */
206: public final void setSendid(final String sendid) {
207: this .sendid = sendid;
208: }
209:
210: /**
211: * Get the target for this <send> element.
212: *
213: * @return String Returns the target.
214: */
215: public final String getTarget() {
216: return target;
217: }
218:
219: /**
220: * Set the target for this <send> element.
221: *
222: * @param target The target to set.
223: */
224: public final void setTarget(final String target) {
225: this .target = target;
226: }
227:
228: /**
229: * Get the target type for this <send> element.
230: *
231: * @return String Returns the targettype.
232: */
233: public final String getTargettype() {
234: return targettype;
235: }
236:
237: /**
238: * Set the target type for this <send> element.
239: *
240: * @param targettype The targettype to set.
241: */
242: public final void setTargettype(final String targettype) {
243: this .targettype = targettype;
244: }
245:
246: /**
247: * Get the event to send.
248: *
249: * @param event The event to set.
250: */
251: public final void setEvent(final String event) {
252: this .event = event;
253: }
254:
255: /**
256: * Set the event to send.
257: *
258: * @return String Returns the event.
259: */
260: public final String getEvent() {
261: return event;
262: }
263:
264: /**
265: * {@inheritDoc}
266: */
267: public void execute(final EventDispatcher evtDispatcher,
268: final ErrorReporter errRep, final SCInstance scInstance,
269: final Log appLog, final Collection derivedEvents)
270: throws ModelException, SCXMLExpressionException {
271: // Send attributes evaluation
272: State parentState = getParentState();
273: Context ctx = scInstance.getContext(parentState);
274: ctx.setLocal(getNamespacesKey(), getNamespaces());
275: Evaluator eval = scInstance.getEvaluator();
276: // Most attributes of <send> are expressions so need to be
277: // evaluated before the EventDispatcher callback
278: Object hintsValue = null;
279: if (!SCXMLHelper.isStringEmpty(hints)) {
280: hintsValue = eval.eval(ctx, hints);
281: }
282: String targetValue = target;
283: if (!SCXMLHelper.isStringEmpty(target)) {
284: targetValue = (String) eval.eval(ctx, target);
285: if (SCXMLHelper.isStringEmpty(targetValue)
286: && appLog.isWarnEnabled()) {
287: appLog.warn("<send>: target expression \"" + target
288: + "\" evaluated to null or empty String");
289: }
290: }
291: String targettypeValue = targettype;
292: if (!SCXMLHelper.isStringEmpty(targettype)) {
293: targettypeValue = (String) eval.eval(ctx, targettype);
294: if (SCXMLHelper.isStringEmpty(targettypeValue)
295: && appLog.isWarnEnabled()) {
296: appLog.warn("<send>: targettype expression \""
297: + 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(
323: TARGETTYPE_SCXML)) {
324: if (SCXMLHelper.isStringEmpty(targetValue)) {
325: // TODO: Remove both short-circuit passes in v1.0
326: if (wait == 0L) {
327: if (appLog.isDebugEnabled()) {
328: appLog.debug("<send>: Enqueued event '" + event
329: + "' with no delay");
330: }
331: derivedEvents.add(new TriggerEvent(event,
332: TriggerEvent.SIGNAL_EVENT));
333: return;
334: }
335: } else {
336: // We know of no other
337: if (appLog.isWarnEnabled()) {
338: appLog.warn("<send>: Unavailable target - "
339: + targetValue);
340: }
341: derivedEvents.add(new TriggerEvent(
342: EVENT_ERR_SEND_TARGETUNAVAILABLE,
343: TriggerEvent.ERROR_EVENT));
344: // short-circuit the EventDispatcher
345: return;
346: }
347: }
348: ctx.setLocal(getNamespacesKey(), null);
349: if (appLog.isDebugEnabled()) {
350: appLog.debug("<send>: Dispatching event '" + event
351: + "' to target '" + targetValue
352: + "' of target type '" + targettypeValue
353: + "' with suggested delay of " + wait + "ms");
354: }
355: // Else, let the EventDispatcher take care of it
356: evtDispatcher.send(sendid, targetValue, targettypeValue, event,
357: params, hintsValue, wait, externalNodes);
358: }
359:
360: /**
361: * Parse delay.
362: *
363: * @param appLog The application log
364: * @return The parsed delay in milliseconds
365: * @throws SCXMLExpressionException If the delay cannot be parsed
366: */
367: private long parseDelay(final Log appLog)
368: throws SCXMLExpressionException {
369:
370: long wait = 0L;
371: long multiplier = 1L;
372:
373: if (!SCXMLHelper.isStringEmpty(delay)) {
374:
375: String trimDelay = delay.trim();
376: String numericDelay = trimDelay;
377: if (trimDelay.endsWith(MILLIS)) {
378: numericDelay = trimDelay.substring(0, trimDelay
379: .length() - 2);
380: } else if (trimDelay.endsWith(SECONDS)) {
381: multiplier = MILLIS_IN_A_SECOND;
382: numericDelay = trimDelay.substring(0, trimDelay
383: .length() - 1);
384: } else if (trimDelay.endsWith(MINUTES)) {
385: multiplier = MILLIS_IN_A_MINUTE;
386: numericDelay = trimDelay.substring(0, trimDelay
387: .length() - 1);
388: }
389:
390: try {
391: wait = Long.parseLong(numericDelay);
392: } catch (NumberFormatException nfe) {
393: appLog.error(nfe.getMessage(), nfe);
394: throw new SCXMLExpressionException(nfe.getMessage(),
395: nfe);
396: }
397: wait *= multiplier;
398:
399: }
400:
401: return wait;
402:
403: }
404:
405: /** The suffix in the delay string for milliseconds. */
406: private static final String MILLIS = "ms";
407:
408: /** The suffix in the delay string for seconds. */
409: private static final String SECONDS = "s";
410:
411: /** The suffix in the delay string for minutes. */
412: private static final String MINUTES = "m";
413:
414: /** The number of milliseconds in a second. */
415: private static final long MILLIS_IN_A_SECOND = 1000L;
416:
417: /** The number of milliseconds in a minute. */
418: private static final long MILLIS_IN_A_MINUTE = 60000L;
419:
420: }
|