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