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.IOException; 020 import java.lang.reflect.InvocationTargetException; 021 import java.lang.reflect.Method; 022 import java.net.URL; 023 024 import org.apache.commons.logging.Log; 025 import org.apache.commons.logging.LogFactory; 026 import org.apache.commons.scxml.Context; 027 import org.apache.commons.scxml.Evaluator; 028 import org.apache.commons.scxml.SCXMLExecutor; 029 import org.apache.commons.scxml.SCXMLListener; 030 import org.apache.commons.scxml.TriggerEvent; 031 import org.apache.commons.scxml.env.jexl.JexlContext; 032 import org.apache.commons.scxml.env.jexl.JexlEvaluator; 033 import org.apache.commons.scxml.io.SCXMLParser; 034 import org.apache.commons.scxml.model.ModelException; 035 import org.apache.commons.scxml.model.SCXML; 036 import org.apache.commons.scxml.model.Transition; 037 import org.apache.commons.scxml.model.TransitionTarget; 038 import org.xml.sax.ErrorHandler; 039 import org.xml.sax.SAXException; 040 041 /** 042 * <p>This class demonstrates one approach for providing the base 043 * functionality needed by classes representing stateful entities, 044 * whose behaviors are defined via SCXML documents.</p> 045 * 046 * <p>SCXML documents (more generically, UML state chart diagrams) can be 047 * used to define stateful behavior of objects, and Commons SCXML enables 048 * developers to use this model directly into the corresponding code 049 * artifacts. The resulting artifacts tend to be much simpler, embody 050 * a useful separation of concerns and are easier to understand and 051 * maintain. As the size of the modeled entity grows, these benefits 052 * become more apparent.</p> 053 * 054 * <p>This approach functions by registering an SCXMLListener that gets 055 * notified onentry, and calls the namesake method for each state that 056 * has been entered.</p> 057 * 058 * <p>This class swallows all exceptions only to log them. Developers of 059 * subclasses should think of themselves as "component developers" 060 * catering to other end users, and therefore ensure that the subclasses 061 * are free of <code>ModelException</code>s and the like. Most methods 062 * are <code>protected</code> for ease of subclassing.</p> 063 * 064 */ 065 public abstract class AbstractStateMachine { 066 067 /** 068 * The state machine that will drive the instances of this class. 069 */ 070 private SCXML stateMachine; 071 072 /** 073 * The instance specific SCXML engine. 074 */ 075 private SCXMLExecutor engine; 076 077 /** 078 * The log. 079 */ 080 private Log log; 081 082 /** 083 * The method signature for the activities corresponding to each 084 * state in the SCXML document. 085 */ 086 private static final Class[] SIGNATURE = new Class[0]; 087 088 /** 089 * The method parameters for the activities corresponding to each 090 * state in the SCXML document. 091 */ 092 private static final Object[] PARAMETERS = new Object[0]; 093 094 /** 095 * Convenience constructor, object instantiation incurs parsing cost. 096 * 097 * @param scxmlDocument The URL pointing to the SCXML document that 098 * describes the "lifecycle" of the 099 * instances of this class. 100 */ 101 public AbstractStateMachine(final URL scxmlDocument) { 102 // default is JEXL 103 this(scxmlDocument, new JexlContext(), new JexlEvaluator()); 104 } 105 106 /** 107 * Primary constructor, object instantiation incurs parsing cost. 108 * 109 * @param scxmlDocument The URL pointing to the SCXML document that 110 * describes the "lifecycle" of the 111 * instances of this class. 112 * @param rootCtx The root context for this instance. 113 * @param evaluator The expression evaluator for this instance. 114 * 115 * @see Context 116 * @see Evaluator 117 */ 118 public AbstractStateMachine(final URL scxmlDocument, 119 final Context rootCtx, final Evaluator evaluator) { 120 log = LogFactory.getLog(this.getClass()); 121 ErrorHandler errHandler = new SimpleErrorHandler(); 122 try { 123 stateMachine = SCXMLParser.parse(scxmlDocument, 124 errHandler); 125 } catch (IOException ioe) { 126 logError(ioe); 127 } catch (SAXException sae) { 128 logError(sae); 129 } catch (ModelException me) { 130 logError(me); 131 } 132 initialize(stateMachine, rootCtx, evaluator); 133 } 134 135 /** 136 * Convenience constructor. 137 * 138 * @param stateMachine The parsed SCXML instance that 139 * describes the "lifecycle" of the 140 * instances of this class. 141 * 142 * @since 0.7 143 */ 144 public AbstractStateMachine(final SCXML stateMachine) { 145 // default is JEXL 146 this(stateMachine, new JexlContext(), new JexlEvaluator()); 147 } 148 149 /** 150 * Primary constructor. 151 * 152 * @param stateMachine The parsed SCXML instance that 153 * describes the "lifecycle" of the 154 * instances of this class. 155 * @param rootCtx The root context for this instance. 156 * @param evaluator The expression evaluator for this instance. 157 * 158 * @see Context 159 * @see Evaluator 160 * 161 * @since 0.7 162 */ 163 public AbstractStateMachine(final SCXML stateMachine, 164 final Context rootCtx, final Evaluator evaluator) { 165 initialize(stateMachine, rootCtx, evaluator); 166 } 167 168 /** 169 * Instantiate and initialize the underlying executor instance. 170 * 171 * @param stateMachine The state machine 172 * @param rootCtx The root context 173 * @param evaluator The expression evaluator 174 */ 175 private void initialize(final SCXML stateMachine, 176 final Context rootCtx, final Evaluator evaluator) { 177 engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(), 178 new SimpleErrorReporter()); 179 engine.setStateMachine(stateMachine); 180 engine.setSuperStep(true); 181 engine.setRootContext(rootCtx); 182 engine.addListener(stateMachine, new EntryListener()); 183 try { 184 engine.go(); 185 } catch (ModelException me) { 186 logError(me); 187 } 188 } 189 190 /** 191 * Fire an event on the SCXML engine. 192 * 193 * @param event The event name. 194 * @return Whether the state machine has reached a "final" 195 * configuration. 196 */ 197 public boolean fireEvent(final String event) { 198 TriggerEvent[] evts = {new TriggerEvent(event, 199 TriggerEvent.SIGNAL_EVENT, null)}; 200 try { 201 engine.triggerEvents(evts); 202 } catch (ModelException me) { 203 logError(me); 204 } 205 return engine.getCurrentStatus().isFinal(); 206 } 207 208 /** 209 * Get the SCXML object representing this state machine. 210 * 211 * @return Returns the stateMachine. 212 * @deprecated Returns null, use getEngine().getStateMachine() instead 213 */ 214 public static SCXML getStateMachine() { 215 return null; 216 } 217 218 /** 219 * Get the SCXML engine driving the "lifecycle" of the 220 * instances of this class. 221 * 222 * @return Returns the engine. 223 */ 224 public SCXMLExecutor getEngine() { 225 return engine; 226 } 227 228 /** 229 * Get the log for this class. 230 * 231 * @return Returns the log. 232 */ 233 public Log getLog() { 234 return log; 235 } 236 237 /** 238 * Set the log for this class. 239 * 240 * @param log The log to set. 241 */ 242 public void setLog(final Log log) { 243 this.log = log; 244 } 245 246 /** 247 * Invoke the no argument method with the following name. 248 * 249 * @param methodName The method to invoke. 250 * @return Whether the invoke was successful. 251 */ 252 public boolean invoke(final String methodName) { 253 Class clas = this.getClass(); 254 try { 255 Method method = clas.getDeclaredMethod(methodName, SIGNATURE); 256 method.invoke(this, PARAMETERS); 257 } catch (SecurityException se) { 258 logError(se); 259 return false; 260 } catch (NoSuchMethodException nsme) { 261 logError(nsme); 262 return false; 263 } catch (IllegalArgumentException iae) { 264 logError(iae); 265 return false; 266 } catch (IllegalAccessException iae) { 267 logError(iae); 268 return false; 269 } catch (InvocationTargetException ite) { 270 logError(ite); 271 return false; 272 } 273 return true; 274 } 275 276 /** 277 * Reset the state machine. 278 * 279 * @return Whether the reset was successful. 280 */ 281 public boolean resetMachine() { 282 try { 283 engine.reset(); 284 } catch (ModelException me) { 285 logError(me); 286 return false; 287 } 288 return true; 289 } 290 291 /** 292 * Utility method for logging error. 293 * 294 * @param exception The exception leading to this error condition. 295 */ 296 protected void logError(final Exception exception) { 297 if (log.isErrorEnabled()) { 298 log.error(exception.getMessage(), exception); 299 } 300 } 301 302 /** 303 * A SCXMLListener that is only concerned about "onentry" 304 * notifications. 305 */ 306 protected class EntryListener implements SCXMLListener { 307 308 /** 309 * {@inheritDoc} 310 */ 311 public void onEntry(final TransitionTarget entered) { 312 invoke(entered.getId()); 313 } 314 315 /** 316 * No-op. 317 * 318 * @param from The "source" transition target. 319 * @param to The "destination" transition target. 320 * @param transition The transition being followed. 321 */ 322 public void onTransition(final TransitionTarget from, 323 final TransitionTarget to, final Transition transition) { 324 // nothing to do 325 } 326 327 /** 328 * No-op. 329 * 330 * @param exited The transition target being exited. 331 */ 332 public void onExit(final TransitionTarget exited) { 333 // nothing to do 334 } 335 336 } 337 338 } 339