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