    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