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
094: .debug("Cancelled event scheduled by <send> with id '"
095: + sendId + "'");
096: }
097: }
098: timers.remove(sendId);
099: }
100:
101: /**
102: @see EventDispatcher#send(String,String,String,String,Map,Object,long,List)
103: */
104: public void send(final String sendId, final String target,
105: final String targettype, final String event,
106: final Map params, final Object hints, final long delay,
107: final List externalNodes) {
108: // Log callback
109: if (log.isInfoEnabled()) {
110: StringBuffer buf = new StringBuffer();
111: buf.append("send ( sendId: ").append(sendId);
112: buf.append(", target: ").append(target);
113: buf.append(", targetType: ").append(targettype);
114: buf.append(", event: ").append(event);
115: buf.append(", params: ").append(String.valueOf(params));
116: buf.append(", hints: ").append(String.valueOf(hints));
117: buf.append(", delay: ").append(delay);
118: buf.append(')');
119: log.info(buf.toString());
120: }
121:
122: // We only handle the "scxml" targettype (which is the default too)
123: if (SCXMLHelper.isStringEmpty(targettype)
124: || targettype.trim().equalsIgnoreCase(TARGETTYPE_SCXML)) {
125:
126: if (!SCXMLHelper.isStringEmpty(target)) {
127: // We know of no other target
128: if (log.isWarnEnabled()) {
129: log.warn("<send>: Unavailable target - " + target);
130: }
131: try {
132: this .executor.triggerEvent(new TriggerEvent(
133: EVENT_ERR_SEND_TARGETUNAVAILABLE,
134: TriggerEvent.ERROR_EVENT));
135: } catch (ModelException me) {
136: log.error(me.getMessage(), me);
137: }
138: return; // done
139: }
140:
141: if (delay > 0L) {
142: // Need to schedule this one
143: Timer timer = new Timer(true);
144: timer.schedule(new DelayedEventTask(sendId, event),
145: delay);
146: timers.put(sendId, timer);
147: if (log.isDebugEnabled()) {
148: log.debug("Scheduled event '" + event
149: + "' with delay " + delay
150: + "ms, as specified by <send> with id '"
151: + sendId + "'");
152: }
153: }
154: // else short-circuited by Send#execute()
155: // TODO: Pass through in v1.0
156:
157: }
158:
159: }
160:
161: /**
162: * Get the log instance.
163: *
164: * @return The current log instance
165: */
166: protected Log getLog() {
167: return log;
168: }
169:
170: /**
171: * Get the current timers.
172: *
173: * @return The currently scheduled timers
174: */
175: protected Map getTimers() {
176: return timers;
177: }
178:
179: /**
180: * Get the executor we're attached to.
181: *
182: * @return The owning executor instance
183: */
184: protected SCXMLExecutor getExecutor() {
185: return executor;
186: }
187:
188: /**
189: * TimerTask implementation.
190: */
191: class DelayedEventTask extends TimerTask {
192:
193: /**
194: * The ID of the <send> element.
195: */
196: private String sendId;
197:
198: /**
199: * The event name.
200: */
201: private String event;
202:
203: /**
204: * Constructor.
205: *
206: * @param sendId The ID of the send element.
207: * @param event The name of the event to be triggered.
208: */
209: DelayedEventTask(final String sendId, final String event) {
210: super ();
211: this .sendId = sendId;
212: this .event = event;
213: }
214:
215: /**
216: * What to do when timer expires.
217: */
218: public void run() {
219: try {
220: executor.triggerEvent(new TriggerEvent(event,
221: TriggerEvent.SIGNAL_EVENT));
222: } catch (ModelException me) {
223: log.error(me.getMessage(), me);
224: }
225: timers.remove(sendId);
226: if (log.isDebugEnabled()) {
227: log.debug("Fired event '" + event
228: + "' as scheduled by " + "<send> with id '"
229: + sendId + "'");
230: }
231: }
232:
233: }
234:
235: /**
236: * The default targettype.
237: */
238: private static final String TARGETTYPE_SCXML = "scxml";
239:
240: /**
241: * The spec mandated derived event when target cannot be reached.
242: */
243: private static final String EVENT_ERR_SEND_TARGETUNAVAILABLE = "error.send.targetunavailable";
244:
245: }
|