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 */ 017package org.apache.commons.scxml2.env; 018 019import java.io.Serializable; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Timer; 024import java.util.TimerTask; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.apache.commons.scxml2.EventDispatcher; 029import org.apache.commons.scxml2.SCXMLIOProcessor; 030import org.apache.commons.scxml2.TriggerEvent; 031 032/** 033 * <p>EventDispatcher implementation that can schedule <code>delay</code>ed 034 * <send> events for the "scxml" <code>type</code> 035 * attribute value (which is also the default). This implementation uses 036 * J2SE <code>Timer</code>s.</p> 037 * 038 * <p>No other <code>type</code>s are processed. Subclasses may support 039 * additional <code>type</code>s by overriding the 040 * <code>send(...)</code> and <code>cancel(...)</code> methods and 041 * delegating to their <code>super</code> counterparts for the 042 * "scxml" <code>type</code>.</p> 043 * 044 */ 045public class SimpleDispatcher implements EventDispatcher, Serializable { 046 047 /** Serial version UID. */ 048 private static final long serialVersionUID = 1L; 049 050 /** 051 * TimerTask implementation. 052 */ 053 class DelayedEventTask extends TimerTask { 054 055 /** 056 * The ID of the <send> element. 057 */ 058 private String id; 059 060 /** 061 * The event name. 062 */ 063 private String event; 064 065 /** 066 * The event payload, if any. 067 */ 068 private Object payload; 069 070 /** 071 * The target io processor 072 */ 073 private SCXMLIOProcessor target; 074 075 /** 076 * Constructor for events with payload. 077 * 078 * @param id The ID of the send element. 079 * @param event The name of the event to be triggered. 080 * @param payload The event payload, if any. 081 * @param target The target io processor 082 */ 083 DelayedEventTask(final String id, final String event, final Object payload, SCXMLIOProcessor target) { 084 super(); 085 this.id = id; 086 this.event = event; 087 this.payload = payload; 088 this.target = target; 089 } 090 091 /** 092 * What to do when timer expires. 093 */ 094 @Override 095 public void run() { 096 timers.remove(id); 097 target.addEvent(new TriggerEvent(event, TriggerEvent.SIGNAL_EVENT, payload)); 098 if (log.isDebugEnabled()) { 099 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 * <send> 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