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