    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.env;
    018
    019    import java.io.Serializable;
    020    import java.util.Collections;
    021    import java.util.HashMap;
    022    import java.util.List;
    023    import java.util.Map;
    024    import java.util.Timer;
    025    import java.util.TimerTask;
    026
    027    import org.apache.commons.logging.Log;
    028    import org.apache.commons.logging.LogFactory;
    029    import org.apache.commons.scxml.EventDispatcher;
    030    import org.apache.commons.scxml.SCXMLExecutor;
    031    import org.apache.commons.scxml.SCXMLHelper;
    032    import org.apache.commons.scxml.TriggerEvent;
    033    import org.apache.commons.scxml.model.ModelException;
    034
    035    /**
    036     * <p>EventDispatcher implementation that can schedule <code>delay</code>ed
    037     * <send> events for the "scxml" <code>targettype</code>
    038     * attribute value (which is also the default). This implementation uses
    039     * J2SE <code>Timer</code>s.</p>
    040     *
    041     * <p>No other <code>targettype</code>s are processed. Subclasses may support
    042     * additional <code>targettype</code>s by overriding the
    043     * <code>send(...)</code> and <code>cancel(...)</code> methods and
    044     * delegating to their <code>super</code> counterparts for the
    045     * "scxml" <code>targettype</code>.</p>
    046     *
    047     */
    048    public class SimpleScheduler implements EventDispatcher, Serializable {
    049
    050        /** Serial version UID. */
    051        private static final long serialVersionUID = 1L;
    052
    053        /** Log instance. */
    054        private Log log = LogFactory.getLog(SimpleScheduler.class);
    055
    056        /**
    057         * The <code>Map</code> of active <code>Timer</code>s, keyed by
    058         * <send> element <code>id</code>s.
    059         */
    060        private Map timers;
    061
    062        /**
    063         * The state chart execution instance we schedule events for.
    064         */
    065        private SCXMLExecutor executor;
    066
    067        /**
    068         * Constructor.
    069         *
    070         * @param executor The owning {@link SCXMLExecutor} instance.
    071         */
    072        public SimpleScheduler(final SCXMLExecutor executor) {
    073            super();
    074            this.executor = executor;
    075            this.timers = Collections.synchronizedMap(new HashMap());
    076        }
    077
    078        /**
    079         * @see EventDispatcher#cancel(String)
    080         */
    081        public void cancel(final String sendId) {
    082            // Log callback
    083            if (log.isInfoEnabled()) {
    084                log.info("cancel( sendId: " + sendId + ")");
    085            }
    086            if (!timers.containsKey(sendId)) {
    087                return; // done, we don't track this one or its already expired
    088            }
    089            Timer timer = (Timer) timers.get(sendId);
    090            if (timer != null) {
    091                timer.cancel();
    092                if (log.isDebugEnabled()) {
    093                    log.debug("Cancelled event scheduled by <send> with id '"
    094                        + sendId + "'");
    095                }
    096            }
    097            timers.remove(sendId);
    098        }
    099
    100        /**
    101        @see EventDispatcher#send(String,String,String,String,Map,Object,long,List)
    102         */
    103        public void send(final String sendId, final String target,
    104                final String targettype, final String event, final Map params,
    105                final Object hints, final long delay, final List externalNodes) {
    106            // Log callback
    107            if (log.isInfoEnabled()) {
    108                StringBuffer buf = new StringBuffer();
    109                buf.append("send ( sendId: ").append(sendId);
    110                buf.append(", target: ").append(target);
    111                buf.append(", targetType: ").append(targettype);
    112                buf.append(", event: ").append(event);
    113                buf.append(", params: ").append(String.valueOf(params));
    114                buf.append(", hints: ").append(String.valueOf(hints));
    115                buf.append(", delay: ").append(delay);
    116                buf.append(')');
    117                log.info(buf.toString());
    118            }
    119
    120            // We only handle the "scxml" targettype (which is the default too)
    121            if (SCXMLHelper.isStringEmpty(targettype)
    122                    || targettype.trim().equalsIgnoreCase(TARGETTYPE_SCXML)) {
    123
    124                if (!SCXMLHelper.isStringEmpty(target)) {
    125                    // We know of no other target
    126                    if (log.isWarnEnabled()) {
    127                        log.warn("<send>: Unavailable target - " + target);
    128                    }
    129                    try {
    130                        this.executor.triggerEvent(new TriggerEvent(
    131                            EVENT_ERR_SEND_TARGETUNAVAILABLE,
    132                            TriggerEvent.ERROR_EVENT));
    133                    } catch (ModelException me) {
    134                        log.error(me.getMessage(), me);
    135                    }
    136                    return; // done
    137                }
    138
    139                if (delay > 0L) {
    140                    // Need to schedule this one
    141                    Timer timer = new Timer(true);
    142                    timer.schedule(new DelayedEventTask(sendId, event, params), delay);
    143                    timers.put(sendId, timer);
    144                    if (log.isDebugEnabled()) {
    145                        log.debug("Scheduled event '" + event + "' with delay "
    146                            + delay + "ms, as specified by <send> with id '"
    147                            + sendId + "'");
    148                    }
    149                }
    150                // else short-circuited by Send#execute()
    151                // TODO: Pass through in v1.0
    152
    153            }
    154
    155        }
    156
    157        /**
    158         * Get the log instance.
    159         *
    160         * @return The current log instance
    161         */
    162        protected Log getLog() {
    163            return log;
    164        }
    165
    166        /**
    167         * Get the current timers.
    168         *
    169         * @return The currently scheduled timers
    170         */
    171        protected Map getTimers() {
    172            return timers;
    173        }
    174
    175        /**
    176         * Get the executor we're attached to.
    177         *
    178         * @return The owning executor instance
    179         */
    180        protected SCXMLExecutor getExecutor() {
    181            return executor;
    182        }
    183
    184        /**
    185         * TimerTask implementation.
    186         */
    187        class DelayedEventTask extends TimerTask {
    188
    189            /**
    190             * The ID of the <send> element.
    191             */
    192            private String sendId;
    193
    194            /**
    195             * The event name.
    196             */
    197            private String event;
    198
    199            /**
    200             * The event payload, if any.
    201             */
    202            private Map payload;
    203
    204            /**
    205             * Constructor.
    206             *
    207             * @param sendId The ID of the send element.
    208             * @param event The name of the event to be triggered.
    209             */
    210            DelayedEventTask(final String sendId, final String event) {
    211                this(sendId, event, null);
    212            }
    213
    214            /**
    215             * Constructor for events with payload.
    216             *
    217             * @param sendId The ID of the send element.
    218             * @param event The name of the event to be triggered.
    219             * @param payload The event payload, if any.
    220             */
    221            DelayedEventTask(final String sendId, final String event,
    222                    final Map payload) {
    223                super();
    224                this.sendId = sendId;
    225                this.event = event;
    226                this.payload = payload;
    227            }
    228
    229            /**
    230             * What to do when timer expires.
    231             */
    232            public void run() {
    233                try {
    234                    executor.triggerEvent(new TriggerEvent(event,
    235                        TriggerEvent.SIGNAL_EVENT, payload));
    236                } catch (ModelException me) {
    237                    log.error(me.getMessage(), me);
    238                }
    239                timers.remove(sendId);
    240                if (log.isDebugEnabled()) {
    241                    log.debug("Fired event '" + event + "' as scheduled by "
    242                        + "<send> with id '" + sendId + "'");
    243                }
    244            }
    245
    246        }
    247
    248        /**
    249         * The default targettype.
    250         */
    251        private static final String TARGETTYPE_SCXML = "scxml";
    252
    253        /**
    254         * The spec mandated derived event when target cannot be reached.
    255         */
    256        private static final String EVENT_ERR_SEND_TARGETUNAVAILABLE =
    257            "error.send.targetunavailable";
    258
    259    }
    260