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.scxml2.env;
18  
19  import java.io.Serializable;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.Timer;
24  import java.util.TimerTask;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.commons.scxml2.EventDispatcher;
29  import org.apache.commons.scxml2.SCXMLIOProcessor;
30  import org.apache.commons.scxml2.TriggerEvent;
31  
32  /**
33   * <p>EventDispatcher implementation that can schedule <code>delay</code>ed
34   * &lt;send&gt; events for the &quot;scxml&quot; <code>type</code>
35   * attribute value (which is also the default). This implementation uses
36   * J2SE <code>Timer</code>s.</p>
37   *
38   * <p>No other <code>type</code>s are processed. Subclasses may support
39   * additional <code>type</code>s by overriding the
40   * <code>send(...)</code> and <code>cancel(...)</code> methods and
41   * delegating to their <code>super</code> counterparts for the
42   * &quot;scxml&quot; <code>type</code>.</p>
43   *
44   */
45  public class SimpleDispatcher implements EventDispatcher, Serializable {
46  
47       /** Serial version UID. */
48      private static final long serialVersionUID = 1L;
49  
50      /**
51       * TimerTask implementation.
52       */
53      class DelayedEventTask extends TimerTask {
54  
55          /**
56           * The ID of the &lt;send&gt; element.
57           */
58          private String id;
59  
60          /**
61           * The event name.
62           */
63          private String event;
64  
65          /**
66           * The event payload, if any.
67           */
68          private Object payload;
69  
70          /**
71           * The target io processor
72           */
73          private SCXMLIOProcessor target;
74  
75          /**
76           * Constructor for events with payload.
77           *
78           * @param id The ID of the send element.
79           * @param event The name of the event to be triggered.
80           * @param payload The event payload, if any.
81           * @param target The target io processor
82           */
83          DelayedEventTask(final String id, final String event, final Object payload, SCXMLIOProcessor target) {
84              super();
85              this.id = id;
86              this.event = event;
87              this.payload = payload;
88              this.target = target;
89          }
90  
91          /**
92           * What to do when timer expires.
93           */
94          @Override
95          public void run() {
96              timers.remove(id);
97              target.addEvent(new TriggerEvent(event, TriggerEvent.SIGNAL_EVENT, payload));
98              if (log.isDebugEnabled()) {
99                  log.debug("Fired event '" + event + "' as scheduled by "
100                         + "<send> with id '" + id + "'");
101             }
102         }
103     }
104 
105     /** Implementation independent log category. */
106      private Log log = LogFactory.getLog(EventDispatcher.class);
107 
108     /**
109      * The <code>Map</code> of active <code>Timer</code>s, keyed by
110      * &lt;send&gt; element <code>id</code>s.
111      */
112     private Map<String, Timer> timers = Collections.synchronizedMap(new HashMap<String, Timer>());
113 
114     /**
115      * Get the log instance.
116      *
117      * @return The current log instance
118      */
119     protected Log getLog() {
120         return log;
121     }
122 
123     /**
124      * Sets the log instance
125      *
126      * @param log the new log instance
127      */
128     protected void setLog(Log log) {
129         this.log = log;
130     }
131 
132     /**
133      * Get the current timers.
134      *
135      * @return The currently scheduled timers
136      */
137     protected Map<String, Timer> getTimers() {
138         return timers;
139     }
140 
141     /**
142      * @see EventDispatcher#cancel(String)
143      */
144     public void cancel(final String sendId) {
145         if (log.isInfoEnabled()) {
146             log.info("cancel( sendId: " + sendId + ")");
147         }
148         if (!timers.containsKey(sendId)) {
149             return; // done, we don't track this one or its already expired
150         }
151         Timer timer = timers.get(sendId);
152         if (timer != null) {
153             timer.cancel();
154             if (log.isDebugEnabled()) {
155                 log.debug("Cancelled event scheduled by <send> with id '"
156                         + sendId + "'");
157             }
158         }
159         timers.remove(sendId);
160     }
161 
162     /**
163     @see EventDispatcher#send(java.util.Map, String, String, String, String, Object, Object, long)
164      */
165     public void send(final Map<String, SCXMLIOProcessor> ioProcessors, final String id, final String target,
166             final String type, final String event, final Object data, final Object hints, final long delay) {
167         if (log.isInfoEnabled()) {
168             StringBuilder buf = new StringBuilder();
169             buf.append("send ( id: ").append(id);
170             buf.append(", target: ").append(target);
171             buf.append(", type: ").append(type);
172             buf.append(", event: ").append(event);
173             buf.append(", data: ").append(String.valueOf(data));
174             buf.append(", hints: ").append(String.valueOf(hints));
175             buf.append(", delay: ").append(delay);
176             buf.append(')');
177             log.info(buf.toString());
178         }
179 
180         // We only handle the "scxml" type (which is the default too) and optionally the #_internal target
181 
182         if (type == null || type.equalsIgnoreCase(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR) ||
183                 type.equals(SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR)) {
184 
185             SCXMLIOProcessor ioProcessor;
186 
187             boolean internal = false;
188 
189             if (target == null) {
190                 ioProcessor = ioProcessors.get(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR);
191             }
192             else if (ioProcessors.containsKey(target)) {
193                 ioProcessor = ioProcessors.get(target);
194                 internal = SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR.equals(target);
195             }
196             else if (SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR.equals(target)) {
197                 ioProcessor = ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR);
198                 internal = true;
199             }
200             else {
201                 // We know of no other target
202                 if (log.isWarnEnabled()) {
203                     log.warn("<send>: Unavailable target - " + target);
204                 }
205                 ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR).
206                         addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
207                 return; // done
208             }
209 
210             if (event == null) {
211                 if (log.isWarnEnabled()) {
212                     log.warn("<send>: Cannot send without event name");
213                 }
214                 ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR).
215                         addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
216             }
217 
218             else if (!internal && delay > 0L) {
219                 // Need to schedule this one
220                 Timer timer = new Timer(true);
221                 timer.schedule(new DelayedEventTask(id, event, data, ioProcessor), delay);
222                 timers.put(id, timer);
223                 if (log.isDebugEnabled()) {
224                     log.debug("Scheduled event '" + event + "' with delay "
225                             + delay + "ms, as specified by <send> with id '"
226                             + id + "'");
227                 }
228             }
229             else {
230                 ioProcessor.addEvent(new TriggerEvent(event, TriggerEvent.SIGNAL_EVENT, data));
231             }
232         }
233         else {
234             if (log.isWarnEnabled()) {
235                 log.warn("<send>: Unsupported type - " + type);
236             }
237             ioProcessors.get(SCXMLIOProcessor.INTERNAL_EVENT_PROCESSOR).
238                     addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
239         }
240     }
241 }
242