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   * &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         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 &lt;send&gt; 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 &lt;send&gt; 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 &lt;send&gt; 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 &lt;send&gt; 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 &lt;send&gt; 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 &lt;send&gt; 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 &lt;send&gt; 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 &lt;send&gt; 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, params));
    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