Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.scxml.io; import java.io.IOException; import java.net.URL; import java.text.MessageFormat; import java.util.Collection; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.digester.Digester; import org.apache.commons.digester.ExtendedBaseRules; import org.apache.commons.digester.NodeCreateRule; import org.apache.commons.digester.ObjectCreateRule; import org.apache.commons.digester.Rule; import org.apache.commons.digester.SetNextRule; import org.apache.commons.digester.SetPropertiesRule; import org.apache.commons.digester.WithDefaultsRulesWrapper; import org.apache.commons.logging.LogFactory; import org.apache.commons.scxml.PathResolver; import org.apache.commons.scxml.SCXMLHelper; import org.apache.commons.scxml.env.URLResolver; import org.apache.commons.scxml.model.Action; import org.apache.commons.scxml.model.Assign; import org.apache.commons.scxml.model.BodyContainer; import org.apache.commons.scxml.model.Cancel; import org.apache.commons.scxml.model.CustomAction; import org.apache.commons.scxml.model.Data; import org.apache.commons.scxml.model.Datamodel; import org.apache.commons.scxml.model.Else; import org.apache.commons.scxml.model.ElseIf; import org.apache.commons.scxml.model.Event; import org.apache.commons.scxml.model.Executable; import org.apache.commons.scxml.model.Exit; import org.apache.commons.scxml.model.ExternalContent; import org.apache.commons.scxml.model.Final; import org.apache.commons.scxml.model.Finalize; import org.apache.commons.scxml.model.History; import org.apache.commons.scxml.model.If; import org.apache.commons.scxml.model.Initial; import org.apache.commons.scxml.model.Invoke; import org.apache.commons.scxml.model.Log; import org.apache.commons.scxml.model.ModelException; import org.apache.commons.scxml.model.NamespacePrefixesHolder; import org.apache.commons.scxml.model.OnEntry; import org.apache.commons.scxml.model.OnExit; import org.apache.commons.scxml.model.Parallel; import org.apache.commons.scxml.model.Param; import org.apache.commons.scxml.model.PathResolverHolder; import org.apache.commons.scxml.model.SCXML; import org.apache.commons.scxml.model.Script; import org.apache.commons.scxml.model.Send; import org.apache.commons.scxml.model.State; import org.apache.commons.scxml.model.Transition; import org.apache.commons.scxml.model.TransitionTarget; import org.apache.commons.scxml.model.Var; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.Attributes; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.SAXException; /** * <p> * The SCXMLParser provides the ability to parse a SCXML document into the Java object model provided in the model package. * </p> * <p> * The SCXMLParser can be used for: * </p> * <ol> * <li>Parse a SCXML file into the Commons SCXML Java object model.</li> * <li>Obtain a SCXML Parser for further customization of the default ruleset.</li> * </ol> * * <p> * If switching from {@link SCXMLDigester}, changes need to be made to the SCXML documents, such as: * </p> * <ul> * <li>A <parallel> should not be wrapped in a <state> element unless otherwise necessary</li> * <li><var> and <exit> elements continue to be supported by Commons SCXML, but in the Commons SCXML namespace: * <code>http://commons.apache.org/scxml</code></li> * <li><event> is now supported</li> * </ul> * <p> * See latest version of the SCXML Working Draft for more details. * </p> * * <p> * <b>NOTE:</b> The SCXMLParser assumes that the SCXML document to be parsed is well-formed and correct. If that assumption does * not hold, any subsequent behavior is undefined. * </p> * * @since 0.7 */ public final class SCXMLParser { /** * The SCXML namespace that this Digester is built for. Any document that is intended to be parsed by this digester * <b>must</b> bind the SCXML elements to this namespace. */ private static final String NAMESPACE_SCXML = "http://www.w3.org/2005/07/scxml"; /** * The namespace that defines any custom actions defined by the Commons SCXML implementation. Any document that intends to use * these custom actions needs to ensure that they are in the correct namespace. Use of actions in this namespace makes the * document non-portable across implementations. */ private static final String NAMESPACE_COMMONS_SCXML = "http://commons.apache.org/scxml"; // ---------------------- PUBLIC METHODS ----------------------// /** * <p> * API for standalone usage where the SCXML document is a URL. * </p> * * @param scxmlURL * a canonical absolute URL to parse (relative URLs within the top level document are to be resovled against this * URL). * @param errHandler * The SAX ErrorHandler * * @return SCXML The SCXML object corresponding to the file argument * * @throws IOException * Underlying Digester parsing threw an IOException * @throws SAXException * Underlying Digester parsing threw a SAXException * @throws ModelException * If the resulting document model has flaws * * @see ErrorHandler * @see PathResolver */ public static SCXML parse(final URL scxmlURL, final ErrorHandler errHandler) throws IOException, SAXException, ModelException { if (scxmlURL == null) { throw new IllegalArgumentException(ERR_NULL_URL); } return parse(scxmlURL, errHandler, null); } /** * <p> * API for standalone usage where the SCXML document is a URI. A PathResolver must be provided. * </p> * * @param pathResolver * The PathResolver for this context * @param documentRealPath * The String pointing to the absolute (real) path of the SCXML document * @param errHandler * The SAX ErrorHandler * * @return SCXML The SCXML object corresponding to the file argument * * @throws IOException * Underlying Digester parsing threw an IOException * @throws SAXException * Underlying Digester parsing threw a SAXException * @throws ModelException * If the resulting document model has flaws * * @see ErrorHandler * @see PathResolver */ public static SCXML parse(final String documentRealPath, final ErrorHandler errHandler, final PathResolver pathResolver) throws IOException, SAXException, ModelException { return parse(documentRealPath, errHandler, pathResolver, null); } /** * <p> * API for standalone usage where the SCXML document is an InputSource. This method may be used when the SCXML document is * packaged in a Java archive, or part of a compound document where the SCXML root is available as a * <code>org.w3c.dom.Element</code> or via a <code>java.io.Reader</code>. * </p> * * <p> * <em>Note:</em> Since there is no path resolution, the SCXML document must not have external state sources. * </p> * * @param documentInputSource * The InputSource for the SCXML document * @param errHandler * The SAX ErrorHandler * * @return SCXML The SCXML object corresponding to the file argument * * @throws IOException * Underlying Digester parsing threw an IOException * @throws SAXException * Underlying Digester parsing threw a SAXException * @throws ModelException * If the resulting document model has flaws * * @see ErrorHandler */ public static SCXML parse(final InputSource documentInputSource, final ErrorHandler errHandler) throws IOException, SAXException, ModelException { if (documentInputSource == null) { throw new IllegalArgumentException(ERR_NULL_ISRC); } return parse(documentInputSource, errHandler, null); } /** * <p> * API for standalone usage where the SCXML document is a URL, and the document uses custom actions. * </p> * * @param scxmlURL * a canonical absolute URL to parse (relative URLs within the top level document are to be resovled against this * URL). * @param errHandler * The SAX ErrorHandler * @param customActions * The list of {@link CustomAction}s this digester instance will process, can be null or empty * * @return SCXML The SCXML object corresponding to the file argument * * @throws IOException * Underlying Digester parsing threw an IOException * @throws SAXException * Underlying Digester parsing threw a SAXException * @throws ModelException * If the resulting document model has flaws * * @see ErrorHandler * @see PathResolver */ public static SCXML parse(final URL scxmlURL, final ErrorHandler errHandler, final List<CustomAction> customActions) throws IOException, SAXException, ModelException { SCXML scxml = null; Digester scxmlParser = SCXMLParser.newInstance(null, new URLResolver(scxmlURL), customActions); scxmlParser.setErrorHandler(errHandler); try { scxml = (SCXML) scxmlParser.parse(scxmlURL.toString()); } catch (RuntimeException rte) { // Intercept runtime exceptions, only to log them with a // sensible error message about failure in document parsing MessageFormat msgFormat = new MessageFormat(ERR_DOC_PARSE_FAIL); String errMsg = msgFormat.format(new Object[] { String.valueOf(scxmlURL), rte.getMessage() }); org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.error(errMsg, rte); throw rte; } if (scxml != null) { ModelUpdater.updateSCXML(scxml); } return scxml; } /** * <p> * API for standalone usage where the SCXML document is a URI. A PathResolver must be provided. * </p> * * @param pathResolver * The PathResolver for this context * @param documentRealPath * The String pointing to the absolute (real) path of the SCXML document * @param errHandler * The SAX ErrorHandler * @param customActions * The list of {@link CustomAction}s this digester instance will process, can be null or empty * * @return SCXML The SCXML object corresponding to the file argument * * @throws IOException * Underlying Digester parsing threw an IOException * @throws SAXException * Underlying Digester parsing threw a SAXException * @throws ModelException * If the resulting document model has flaws * * @see ErrorHandler * @see PathResolver */ public static SCXML parse(final String documentRealPath, final ErrorHandler errHandler, final PathResolver pathResolver, final List<CustomAction> customActions) throws IOException, SAXException, ModelException { if (documentRealPath == null) { throw new IllegalArgumentException(ERR_NULL_PATH); } SCXML scxml = null; Digester scxmlParser = SCXMLParser.newInstance(null, pathResolver, customActions); scxmlParser.setErrorHandler(errHandler); try { scxml = (SCXML) scxmlParser.parse(documentRealPath); } catch (RuntimeException rte) { // Intercept runtime exceptions, only to log them with a // sensible error message about failure in document parsing MessageFormat msgFormat = new MessageFormat(ERR_DOC_PARSE_FAIL); String errMsg = msgFormat.format(new Object[] { documentRealPath, rte.getMessage() }); org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.error(errMsg, rte); throw rte; } if (scxml != null) { ModelUpdater.updateSCXML(scxml); } return scxml; } /** * <p> * API for standalone usage where the SCXML document is an InputSource. This method may be used when the SCXML document is * packaged in a Java archive, or part of a compound document where the SCXML root is available as a * <code>org.w3c.dom.Element</code> or via a <code>java.io.Reader</code>. * </p> * * <p> * <em>Note:</em> Since there is no path resolution, the SCXML document must not have external state sources. * </p> * * @param documentInputSource * The InputSource for the SCXML document * @param errHandler * The SAX ErrorHandler * @param customActions * The list of {@link CustomAction}s this digester instance will process, can be null or empty * * @return SCXML The SCXML object corresponding to the file argument * * @throws IOException * Underlying Digester parsing threw an IOException * @throws SAXException * Underlying Digester parsing threw a SAXException * @throws ModelException * If the resulting document model has flaws * * @see ErrorHandler */ public static SCXML parse(final InputSource documentInputSource, final ErrorHandler errHandler, final List<CustomAction> customActions) throws IOException, SAXException, ModelException { Digester scxmlParser = SCXMLParser.newInstance(null, null, customActions); scxmlParser.setErrorHandler(errHandler); SCXML scxml = null; try { scxml = (SCXML) scxmlParser.parse(documentInputSource); } catch (RuntimeException rte) { // Intercept runtime exceptions, only to log them with a // sensible error message about failure in document parsing org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.error(ERR_ISRC_PARSE_FAIL, rte); throw rte; } if (scxml != null) { ModelUpdater.updateSCXML(scxml); } return scxml; } /** * <p> * Obtain a SCXML digester instance for further customization. * </p> * <b>API Notes:</b> * <ul> * <li>Use the digest() convenience methods if you do not need a custom digester.</li> * <li>After the SCXML document is parsed by the customized digester, the object model <b>must</b> be made executor-ready by * calling <code>updateSCXML(SCXML)</code> method in this class.</li> * </ul> * * @return Digester A newly configured SCXML digester instance * * @see SCXMLParser#updateSCXML(SCXML) */ public static Digester newInstance() { return newInstance(null, null, null); } /** * <p> * Obtain a SCXML digester instance for further customization. * </p> * <b>API Notes:</b> * <ul> * <li>Use the digest() convenience methods if you do not need a custom digester.</li> * <li>After the SCXML document is parsed by the customized digester, the object model <b>must</b> be made executor-ready by * calling <code>updateSCXML(SCXML)</code> method in this class.</li> * </ul> * * @param pr * The PathResolver, may be null for standalone documents * @return Digester A newly configured SCXML digester instance * * @see SCXMLParser#updateSCXML(SCXML) */ public static Digester newInstance(final PathResolver pr) { return newInstance(null, pr, null); } /** * <p> * Obtain a SCXML digester instance for further customization. * </p> * <b>API Notes:</b> * <ul> * <li>Use the digest() convenience methods if you do not need a custom digester.</li> * <li>After the SCXML document is parsed by the customized digester, the object model <b>must</b> be made executor-ready by * calling <code>updateSCXML(SCXML)</code> method in this class.</li> * </ul> * * @param scxml * The parent SCXML document if there is one (in case of state templates for example), null otherwise * @param pr * The PathResolver, may be null for standalone documents * @return Digester A newly configured SCXML digester instance * * @see SCXMLParser#updateSCXML(SCXML) */ public static Digester newInstance(final SCXML scxml, final PathResolver pr) { return newInstance(scxml, pr, null); } /** * <p> * Obtain a SCXML digester instance for further customization. * </p> * <b>API Notes:</b> * <ul> * <li>Use the digest() convenience methods if you do not need a custom digester.</li> * <li>After the SCXML document is parsed by the customized digester, the object model <b>must</b> be made executor-ready by * calling <code>updateSCXML(SCXML)</code> method in this class.</li> * </ul> * * @param scxml * The parent SCXML document if there is one (in case of state templates for example), null otherwise * @param pr * The PathResolver, may be null for standalone documents * @param customActions * The list of {@link CustomAction}s this digester instance will process, can be null or empty * @return Digester A newly configured SCXML digester instance * * @see SCXMLParser#updateSCXML(SCXML) */ public static Digester newInstance(final SCXML scxml, final PathResolver pr, final List<CustomAction> customActions) { Digester digester = new Digester(); digester.setNamespaceAware(true); // Uncomment next line after SCXML DTD is available // digester.setValidating(true); WithDefaultsRulesWrapper rules = new WithDefaultsRulesWrapper(initRules(scxml, pr, customActions)); rules.addDefault(new IgnoredElementRule()); digester.setRules(rules); return digester; } /** * <p> * Update the SCXML object model and make it SCXMLExecutor ready. This is part of post-digester processing, and sets up the * necessary object references throughtout the SCXML object model for the parsed document. Should be used only if a customized * digester obtained using the <code>newInstance()</code> methods is needed. * </p> * * @param scxml * The SCXML object (output from Digester) * @throws ModelException * If the document model has flaws */ public static void updateSCXML(final SCXML scxml) throws ModelException { ModelUpdater.updateSCXML(scxml); } // ---------------------- PRIVATE CONSTANTS ----------------------// // // Patterns to get the digestion going, prefixed by XP_ /** Root <scxml> element. */ private static final String XP_SM = "scxml"; /** <state> children of root <scxml> element. */ private static final String XP_SM_ST = "scxml/state"; /** <state> children of root <scxml> element. */ private static final String XP_SM_PAR = "scxml/parallel"; /** <final> children of root <scxml> element. */ private static final String XP_SM_FIN = "scxml/final"; // // Universal matches, prefixed by XPU_ // State /** <state> children of <state> elements. */ private static final String XPU_ST_ST = "!*/state/state"; /** <final> children of <state> elements. */ private static final String XPU_ST_FIN = "!*/state/final"; /** <state> children of <parallel> elements. */ private static final String XPU_PAR_ST = "!*/parallel/state"; // Parallel /** <parallel> child of <state> elements. */ private static final String XPU_ST_PAR = "!*/state/parallel"; // If /** <if> element. */ private static final String XPU_IF = "!*/if"; // Executables, next three patterns useful when adding custom actions /** <onentry> element. */ private static final String XPU_ONEN = "!*/onentry"; /** <onexit> element. */ private static final String XPU_ONEX = "!*/onexit"; /** <transition> element. */ private static final String XPU_TR = "!*/transition"; /** <finalize> element. */ private static final String XPU_FIN = "!*/finalize"; // // Path Fragments, constants prefixed by XPF_ // Onentries and Onexits /** <onentry> child element. */ private static final String XPF_ONEN = "/onentry"; /** <onexit> child element. */ private static final String XPF_ONEX = "/onexit"; // Datamodel section /** <datamodel> child element. */ private static final String XPF_DM = "/datamodel"; /** Individual <data> elements. */ private static final String XPF_DATA = "/data"; // Initial /** <initial> child element. */ private static final String XPF_INI = "/initial"; // Invoke, param and finalize /** <invoke> child element of <state>. */ private static final String XPF_INV = "/invoke"; /** <param> child element of <invoke>. */ private static final String XPF_PRM = "/param"; /** <finalize> child element of <invoke>. */ private static final String XPF_FIN = "/finalize"; // History /** <history> child element. */ private static final String XPF_HIST = "/history"; // Transition, target and exit /** <transition> child element. */ private static final String XPF_TR = "/transition"; /** <exit> child element, a Commons SCXML custom action. */ private static final String XPF_EXT = "/exit"; // Actions /** <assign> child element. */ private static final String XPF_ASN = "/assign"; /** <event> child element. */ private static final String XPF_EVT = "/event"; /** <send> child element. */ private static final String XPF_SND = "/send"; /** <cancel> child element. */ private static final String XPF_CAN = "/cancel"; /** <elseif> child element. */ private static final String XPF_EIF = "/elseif"; /** <else> child element. */ private static final String XPF_ELS = "/else"; // Custom Commons SCXML actions /** <var> child element. */ private static final String XPF_VAR = "/var"; /** <log> child element. */ private static final String XPF_LOG = "/log"; /** <script> child element. */ private static final String XPF_SCRIPT = "/script"; // // Other constants // Error messages /** * Null URL passed as argument. */ private static final String ERR_NULL_URL = "Cannot parse null URL"; /** * Null path passed as argument. */ private static final String ERR_NULL_PATH = "Cannot parse null URL"; /** * Null InputSource passed as argument. */ private static final String ERR_NULL_ISRC = "Cannot parse null URL"; /** * Parsing SCXML document has failed. */ private static final String ERR_DOC_PARSE_FAIL = "Error parsing " + "SCXML document: \"{0}\", with message: \"{1}\"\n"; /** * Parsing SCXML document InputSource has failed. */ private static final String ERR_ISRC_PARSE_FAIL = "Could not parse SCXML InputSource"; /** * Parser configuration error while registering data rule. */ private static final String ERR_PARSER_CFG_DATA = "XML Parser " + "misconfiguration, error registering <data> element rule"; /** * Parser configuration error while registering send rule. */ private static final String ERR_PARSER_CFG_SEND = "XML Parser " + "misconfiguration, error registering <send> element rule"; /** * Parser configuration error while registering body content rule for custom action. */ private static final String ERR_PARSER_CFG_CUSTOM = "XML Parser " + "misconfiguration, error registering custom action rules"; /** * Error message while attempting to define a custom action which does not extend the Commons SCXML Action base class. */ private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list" + " contained unknown object (not a Commons SCXML Action subtype)"; /** * Error message when the URI in a <state>'s "src" attribute does not point to a valid SCXML document, and * thus cannot be parsed. */ private static final String ERR_STATE_SRC = "Source attribute in <state src=\"{0}\"> cannot be parsed"; /** * Error message when the target of the URI fragment in a <state>'s "src" attribute is not defined in the * referenced document. */ private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in " + "<state src=\"{0}\"> is an unknown state in referenced document"; /** * Error message when the target of the URI fragment in a <state>'s "src" attribute is not a <state> or * <final> in the referenced document. */ private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment" + " in <state src=\"{0}\"> does not point to a <state> or <final>"; // String constants /** Slash. */ private static final String STR_SLASH = "/"; // ---------------------- PRIVATE UTILITY METHODS ----------------------// /* * Private utility functions for configuring digester rule base for SCXML. */ /** * Initialize the Digester rules for the current document. * * @param scxml * The parent SCXML document (or null) * @param pr * The PathResolver * @param customActions * The list of custom actions this digester needs to be able to process * * @return scxmlRules The rule set to be used for digestion */ private static ExtendedBaseRules initRules(final SCXML scxml, final PathResolver pr, final List<CustomAction> customActions) { ExtendedBaseRules scxmlRules = new ExtendedBaseRules(); scxmlRules.setNamespaceURI(NAMESPACE_SCXML); // // SCXML scxmlRules.add(XP_SM, new ObjectCreateRule(SCXML.class)); scxmlRules.add(XP_SM, new SetPropertiesRule()); scxmlRules.add(XP_SM, new SetCurrentNamespacesRule()); // // Datamodel at document root i.e. <scxml> datamodel addDatamodelRules(XP_SM + XPF_DM, scxmlRules, scxml, pr); // // States // Level one states addStateRules(XP_SM_ST, scxmlRules, customActions, scxml, pr); // Nested states addStateRules(XPU_ST_ST, scxmlRules, customActions, scxml, pr); // Orthogonal states addStateRules(XPU_PAR_ST, scxmlRules, customActions, scxml, pr); // // Parallels // Level one parallels addParallelRules(XP_SM_PAR, scxmlRules, customActions, scxml, pr); // Parallel children of composite states addParallelRules(XPU_ST_PAR, scxmlRules, customActions, scxml, pr); // // Finals // Level one finals addFinalRules(XP_SM_FIN, scxmlRules, customActions, scxml, pr); // Final children of composite states addFinalRules(XPU_ST_FIN, scxmlRules, customActions, scxml, pr); // // Ifs addIfRules(XPU_IF, scxmlRules, pr, customActions); // // Custom actions addCustomActionRules(XPU_ONEN, scxmlRules, customActions); addCustomActionRules(XPU_ONEX, scxmlRules, customActions); addCustomActionRules(XPU_TR, scxmlRules, customActions); addCustomActionRules(XPU_IF, scxmlRules, customActions); addCustomActionRules(XPU_FIN, scxmlRules, customActions); return scxmlRules; } /** * Add Digester rules for all <state> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process * @param scxml * The parent SCXML document (or null) * @param pr * The PathResolver */ private static void addStateRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final SCXML scxml, final PathResolver pr) { scxmlRules.add(xp, new ObjectCreateRule(State.class)); addStatePropertiesRules(xp, scxmlRules, customActions, pr, scxml); addDatamodelRules(xp + XPF_DM, scxmlRules, scxml, pr); addInvokeRules(xp + XPF_INV, scxmlRules, customActions, pr, scxml); addInitialRules(xp + XPF_INI, scxmlRules, customActions, pr, scxml); addHistoryRules(xp + XPF_HIST, scxmlRules, customActions, pr, scxml); addTransitionRules(xp + XPF_TR, scxmlRules, "addTransition", pr, customActions); addHandlerRules(xp, scxmlRules, pr, customActions); scxmlRules.add(xp, new UpdateModelRule(scxml)); scxmlRules.add(xp, new SetNextRule("addChild")); } /** * Add Digester rules for all <parallel> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process * @param pr * The {@link PathResolver} for this document * @param scxml * The parent SCXML document (or null) */ private static void addParallelRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final SCXML scxml, final PathResolver pr) { addSimpleRulesTuple(xp, scxmlRules, Parallel.class, null, null, "addChild"); addDatamodelRules(xp + XPF_DM, scxmlRules, scxml, pr); //TODO: added history rule (dp) addHistoryRules(xp + XPF_HIST, scxmlRules, customActions, pr, scxml); addTransitionRules(xp + XPF_TR, scxmlRules, "addTransition", pr, customActions); addHandlerRules(xp, scxmlRules, pr, customActions); scxmlRules.add(xp, new UpdateModelRule(scxml)); } /** * Add Digester rules for all <final> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process * @param scxml * The parent SCXML document (or null) * @param pr * The {@link PathResolver} for this document */ private static void addFinalRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final SCXML scxml, final PathResolver pr) { addSimpleRulesTuple(xp, scxmlRules, Final.class, null, null, "addChild"); addHandlerRules(xp, scxmlRules, pr, customActions); scxmlRules.add(xp, new UpdateModelRule(scxml)); } /** * Add Digester rules for all <state> element attributes. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process * @param pr * The PathResolver * @param scxml * The root document, if this one is src'ed in */ private static void addStatePropertiesRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final PathResolver pr, final SCXML scxml) { scxmlRules.add(xp, new SetPropertiesRule(new String[] { "id", "final", "initial" }, new String[] { "id", "final", "first" })); scxmlRules.add(xp, new DigestSrcAttributeRule(scxml, customActions, pr)); } /** * Add Digester rules for all <datamodel> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param pr * The PathResolver * @param scxml * The parent SCXML document (or null) */ private static void addDatamodelRules(final String xp, final ExtendedBaseRules scxmlRules, final SCXML scxml, final PathResolver pr) { scxmlRules.add(xp, new ObjectCreateRule(Datamodel.class)); scxmlRules.add(xp + XPF_DATA, new ObjectCreateRule(Data.class)); scxmlRules.add(xp + XPF_DATA, new SetPropertiesRule()); scxmlRules.add(xp + XPF_DATA, new SetCurrentNamespacesRule()); scxmlRules.add(xp + XPF_DATA, new SetNextRule("addData")); try { scxmlRules.add(xp + XPF_DATA, new ParseDataRule(pr)); } catch (ParserConfigurationException pce) { org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.error(ERR_PARSER_CFG_DATA, pce); } scxmlRules.add(xp, new SetNextRule("setDatamodel")); } /** * Add Digester rules for all <invoke> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of {@link CustomAction}s this digester instance will process, can be null or empty * @param pr * The PathResolver * @param scxml * The parent SCXML document (or null) */ private static void addInvokeRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final PathResolver pr, final SCXML scxml) { scxmlRules.add(xp, new ObjectCreateRule(Invoke.class)); scxmlRules.add(xp, new SetPropertiesRule()); scxmlRules.add(xp, new SetCurrentNamespacesRule()); scxmlRules.add(xp, new SetPathResolverRule(pr)); scxmlRules.add(xp + XPF_PRM, new ObjectCreateRule(Param.class)); scxmlRules.add(xp + XPF_PRM, new SetPropertiesRule()); scxmlRules.add(xp + XPF_PRM, new SetCurrentNamespacesRule()); scxmlRules.add(xp + XPF_PRM, new SetNextRule("addParam")); scxmlRules.add(xp + XPF_FIN, new ObjectCreateRule(Finalize.class)); scxmlRules.add(xp + XPF_FIN, new UpdateFinalizeRule()); addActionRules(xp + XPF_FIN, scxmlRules, pr, customActions); scxmlRules.add(xp + XPF_FIN, new SetNextRule("setFinalize")); scxmlRules.add(xp, new SetNextRule("setInvoke")); } /** * Add Digester rules for all <initial> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process * @param pr * The PathResolver * @param scxml * The parent SCXML document (or null) */ private static void addInitialRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final PathResolver pr, final SCXML scxml) { scxmlRules.add(xp, new ObjectCreateRule(Initial.class)); addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr, scxml); scxmlRules.add(xp, new UpdateModelRule(scxml)); addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition", pr, customActions); scxmlRules.add(xp, new SetNextRule("setInitial")); } /** * Add Digester rules for all <history> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process * @param pr * The PathResolver * @param scxml * The parent SCXML document (or null) */ private static void addHistoryRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final PathResolver pr, final SCXML scxml) { scxmlRules.add(xp, new ObjectCreateRule(History.class)); addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr, scxml); scxmlRules.add(xp, new UpdateModelRule(scxml)); scxmlRules.add(xp, new SetPropertiesRule(new String[] { "type" }, new String[] { "type" })); addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition", pr, customActions); scxmlRules.add(xp, new SetNextRule("addHistory")); } /** * Add Digester rules for all pseudo state (initial, history) element attributes. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process * @param pr * The PathResolver * @param scxml * The root document, if this one is src'ed in */ private static void addPseudoStatePropertiesRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions, final PathResolver pr, final SCXML scxml) { scxmlRules.add(xp, new SetPropertiesRule(new String[] { "id" }, new String[] { "id" })); scxmlRules.add(xp, new DigestSrcAttributeRule(scxml, customActions, pr)); } /** * Add Digester rules for all <transition> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param setNextMethod * The method name for adding this transition to its parent (defined by the SCXML Java object model). * @param pr * The {@link PathResolver} for this document * @param customActions * The list of custom actions this digester needs to be able to process */ private static void addTransitionRules(final String xp, final ExtendedBaseRules scxmlRules, final String setNextMethod, final PathResolver pr, final List<CustomAction> customActions) { scxmlRules.add(xp, new ObjectCreateRule(Transition.class)); scxmlRules.add(xp, new SetPropertiesRule(new String[] { "event", "cond", "target" }, new String[] { "event", "cond", "next" })); scxmlRules.add(xp, new SetCurrentNamespacesRule()); addActionRules(xp, scxmlRules, pr, customActions); // Add <exit> custom action rule in Commons SCXML namespace scxmlRules.setNamespaceURI(NAMESPACE_COMMONS_SCXML); scxmlRules.add(xp + XPF_EXT, new Rule() { @Override public void end(final String namespace, final String name) { Transition t = (Transition) getDigester().peek(1); TransitionTarget tt = (TransitionTarget) getDigester().peek(2); if (tt instanceof Initial) { org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.warn("Ignored <exit> action in <initial>"); } else { State exitState = new State(); exitState.setFinal(true); t.getTargets().add(exitState); } } }); scxmlRules.setNamespaceURI(NAMESPACE_SCXML); scxmlRules.add(xp, new SetNextRule(setNextMethod)); } /** * Add Digester rules for all <onentry> and <onexit> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param pr * The {@link PathResolver} for this document * @param customActions * The list of custom actions this digester needs to be able to process */ private static void addHandlerRules(final String xp, final ExtendedBaseRules scxmlRules, final PathResolver pr, final List<CustomAction> customActions) { scxmlRules.add(xp + XPF_ONEN, new ObjectCreateRule(OnEntry.class)); addActionRules(xp + XPF_ONEN, scxmlRules, pr, customActions); scxmlRules.add(xp + XPF_ONEN, new SetNextRule("setOnEntry")); scxmlRules.add(xp + XPF_ONEX, new ObjectCreateRule(OnExit.class)); addActionRules(xp + XPF_ONEX, scxmlRules, pr, customActions); scxmlRules.add(xp + XPF_ONEX, new SetNextRule("setOnExit")); } /** * Add Digester rules for all actions ("executable" elements). * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param pr * The {@link PathResolver} for this document * @param customActions * The list of custom actions this digester needs to be able to process */ private static void addActionRules(final String xp, final ExtendedBaseRules scxmlRules, final PathResolver pr, final List<CustomAction> customActions) { // Actions in SCXML namespace addActionRulesTuple(xp + XPF_ASN, scxmlRules, Assign.class); scxmlRules.add(xp + XPF_ASN, new SetPathResolverRule(pr)); addActionRulesTuple(xp + XPF_EVT, scxmlRules, Event.class); addSendRulesTuple(xp + XPF_SND, scxmlRules); addActionRulesTuple(xp + XPF_CAN, scxmlRules, Cancel.class); addActionRulesTuple(xp + XPF_LOG, scxmlRules, Log.class); // Script addActionRulesTuple(xp + XPF_SCRIPT, scxmlRules, Script.class); scxmlRules.add(xp + XPF_SCRIPT, new SetBodyRule()); // Actions in Commons SCXML namespace scxmlRules.setNamespaceURI(NAMESPACE_COMMONS_SCXML); addActionRulesTuple(xp + XPF_VAR, scxmlRules, Var.class); addActionRulesTuple(xp + XPF_EXT, scxmlRules, Exit.class); // Reset namespace scxmlRules.setNamespaceURI(NAMESPACE_SCXML); } /** * Add custom action rules, if any custom actions are provided. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param customActions * The list of custom actions this digester needs to be able to process */ private static void addCustomActionRules(final String xp, final ExtendedBaseRules scxmlRules, final List<CustomAction> customActions) { if (customActions == null || customActions.size() == 0) { return; } for (CustomAction ca : customActions) { if (ca == null) { org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.warn(ERR_CUSTOM_ACTION_TYPE); } else { scxmlRules.setNamespaceURI(ca.getNamespaceURI()); String xpfLocalName = STR_SLASH + ca.getLocalName(); Class<? extends Action> klass = ca.getActionClass(); if (SCXMLHelper.implementationOf(klass, ExternalContent.class)) { addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules, klass, true); } else { addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules, klass, false); } } } scxmlRules.setNamespaceURI(NAMESPACE_SCXML); } /** * Add Digester rules that are specific to the <send> action element. * * @param xp * The Digester style XPath expression of <send> element * @param scxmlRules * The rule set to be used for digestion */ private static void addSendRulesTuple(final String xp, final ExtendedBaseRules scxmlRules) { addActionRulesTuple(xp, scxmlRules, Send.class); try { scxmlRules.add(xp, new ParseExternalContentRule()); } catch (ParserConfigurationException pce) { org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.error(ERR_PARSER_CFG_SEND, pce); } } /** * Add Digester rules for a simple custom action (no body content). * * @param xp * The path to the custom action element * @param scxmlRules * The rule set to be used for digestion * @param klass * The <code>Action</code> class implementing the custom action. * @param bodyContent * Whether the custom rule has body content that should be parsed using <code>NodeCreateRule</code> */ private static void addCustomActionRulesTuple(final String xp, final ExtendedBaseRules scxmlRules, final Class<? extends Action> klass, final boolean bodyContent) { addActionRulesTuple(xp, scxmlRules, klass); if (bodyContent) { try { scxmlRules.add(xp, new ParseExternalContentRule()); } catch (ParserConfigurationException pce) { org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.error(ERR_PARSER_CFG_CUSTOM, pce); } } } /** * Add Digester rules for all <if> elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param pr * The {@link PathResolver} for this document * @param customActions * The list of custom actions this digester needs to be able to process */ private static void addIfRules(final String xp, final ExtendedBaseRules scxmlRules, final PathResolver pr, final List<CustomAction> customActions) { addActionRulesTuple(xp, scxmlRules, If.class); addActionRules(xp, scxmlRules, pr, customActions); addActionRulesTuple(xp + XPF_EIF, scxmlRules, ElseIf.class); addActionRulesTuple(xp + XPF_ELS, scxmlRules, Else.class); } /** * Add Digester rules that are common across all actions elements. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param klass * The class in the Java object model to be instantiated in the ObjectCreateRule for this action */ private static void addActionRulesTuple(final String xp, final ExtendedBaseRules scxmlRules, final Class<? extends Action> klass) { addSimpleRulesTuple(xp, scxmlRules, klass, null, null, "addAction"); scxmlRules.add(xp, new SetExecutableParentRule()); scxmlRules.add(xp, new SetCurrentNamespacesRule()); } /** * Add the run of the mill Digester rules for any element. * * @param xp * The Digester style XPath expression of the parent XML element * @param scxmlRules * The rule set to be used for digestion * @param klass * The class in the Java object model to be instantiated in the ObjectCreateRule for this action * @param args * The attributes to be mapped into the object model * @param props * The properties that args get mapped to * @param addMethod * The method that the SetNextRule should call */ private static void addSimpleRulesTuple(final String xp, final ExtendedBaseRules scxmlRules, final Class<?> klass, final String[] args, final String[] props, final String addMethod) { scxmlRules.add(xp, new ObjectCreateRule(klass)); if (args == null) { scxmlRules.add(xp, new SetPropertiesRule()); } else { scxmlRules.add(xp, new SetPropertiesRule(args, props)); } scxmlRules.add(xp, new SetNextRule(addMethod)); } /** * Discourage instantiation since this is a utility class. */ private SCXMLParser() { super(); } /** * Custom digestion rule for establishing necessary associations of this TransitionTarget with the root SCXML object. These * include: <br> * 1) Updation of the SCXML object's global targets Map <br> * 2) Obtaining a handle to the SCXML object's NotificationRegistry <br> */ private static class UpdateModelRule extends Rule { /** * The root SCXML object. */ private SCXML scxml; /** * Constructor. * * @param scxml * The root SCXML object */ UpdateModelRule(final SCXML scxml) { super(); this.scxml = scxml; } /** * @see Rule#end(String, String) */ @Override public final void end(final String namespace, final String name) { if (scxml == null) { scxml = (SCXML) getDigester().peek(getDigester().getCount() - 1); } TransitionTarget tt = (TransitionTarget) getDigester().peek(); scxml.addTarget(tt); } } /** * Custom digestion rule for setting Executable parent of Action elements. */ private static class SetExecutableParentRule extends Rule { /** * Constructor. */ SetExecutableParentRule() { super(); } /** * @see Rule#end(String, String) */ @Override public final void end(final String namespace, final String name) { Action child = (Action) getDigester().peek(); for (int i = 1; i < getDigester().getCount() - 1; i++) { Object ancestor = getDigester().peek(i); if (ancestor instanceof Executable) { child.setParent((Executable) ancestor); return; } } } } /** * Custom digestion rule for parsing bodies of <code>ExternalContent</code> elements. * * @see ExternalContent */ private static class ParseExternalContentRule extends NodeCreateRule { /** * Constructor. * * @throws ParserConfigurationException * A JAXP configuration error */ ParseExternalContentRule() throws ParserConfigurationException { super(); } /** * @see Rule#end(String, String) */ @Override public final void end(final String namespace, final String name) { Element bodyElement = (Element) getDigester().pop(); NodeList childNodes = bodyElement.getChildNodes(); List<Node> externalNodes = ((ExternalContent) getDigester().peek()).getExternalNodes(); for (int i = 0; i < childNodes.getLength(); i++) { externalNodes.add(childNodes.item(i)); } } } /** * Custom digestion rule for parsing bodies of <data> elements. */ private static class ParseDataRule extends NodeCreateRule { /** * The PathResolver used to resolve the src attribute to the SCXML document it points to. * * @see PathResolver */ private final PathResolver pr; /** * The "src" attribute, retained to check if body content is legal. */ private String src; /** * The "expr" attribute, retained to check if body content is legal. */ private String expr; /** * The XML tree for this data, parse as a Node, obtained from either the "src" or the "expr" attributes. */ private Node attrNode; /** * Constructor. * * @param pr * The <code>PathResolver</code> * @throws ParserConfigurationException * A JAXP configuration error */ ParseDataRule(final PathResolver pr) throws ParserConfigurationException { super(); this.pr = pr; } /** * @see Rule#begin(String, String, Attributes) */ @Override public final void begin(final String namespace, final String name, final Attributes attributes) throws Exception { super.begin(namespace, name, attributes); src = attributes.getValue("src"); expr = attributes.getValue("expr"); if (!SCXMLHelper.isStringEmpty(src)) { String path = null; if (pr == null) { path = src; } else { path = pr.resolvePath(src); } try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbFactory.newDocumentBuilder(); attrNode = db.parse(path); } catch (FactoryConfigurationError t) { logError(t); } catch (ParserConfigurationException t) { logError(t); } catch (SAXException t) { logError(t); } catch (IOException t) { logError(t); } return; } } /** * @param throwable */ private void logError(Throwable throwable) { org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); log.error(throwable.getMessage(), throwable); } /** * @see Rule#end(String, String) */ @Override public final void end(final String namespace, final String name) { Node bodyNode = (Node) getDigester().pop(); Data data = ((Data) getDigester().peek()); // Prefer "src" over "expr", "expr" over child nodes // "expr" can only be evaluated at execution time if (!SCXMLHelper.isStringEmpty(src)) { data.setNode(attrNode); } else if (SCXMLHelper.isStringEmpty(expr)) { // both "src" and "expr" are empty data.setNode(bodyNode); } } } /** * Custom digestion rule for external sources, that is, the src attribute of the <state> element. */ private static class DigestSrcAttributeRule extends Rule { /** * The PathResolver used to resolve the src attribute to the SCXML document it points to. * * @see PathResolver */ private final PathResolver pr; /** * The root document. */ private SCXML root; /** * The list of custom actions the parent document is capable of processing (and hence, the child should be, by * transitivity). * * @see CustomAction */ private final List<CustomAction> customActions; /** * Constructor. * * @param pr * The PathResolver * @param customActions * The list of custom actions this digester needs to be able to process * * @see PathResolver * @see CustomAction */ DigestSrcAttributeRule(final List<CustomAction> customActions, final PathResolver pr) { super(); this.customActions = customActions; this.pr = pr; } /** * Constructor. * * @param root * The root document, if this one is src'ed in * @param pr * The PathResolver * @param customActions * The list of custom actions this digester needs to be able to process * * @see PathResolver * @see CustomAction */ DigestSrcAttributeRule(final SCXML root, final List<CustomAction> customActions, final PathResolver pr) { super(); this.root = root; this.customActions = customActions; this.pr = pr; } /** * @see Rule#begin(String, String, Attributes) */ @Override public final void begin(final String namespace, final String name, final Attributes attributes) throws ModelException { String src = attributes.getValue("src"); if (SCXMLHelper.isStringEmpty(src)) { return; } // 1) Digest the external SCXML file Digester digester = getDigester(); SCXML scxml = (SCXML) digester.peek(digester.getCount() - 1); SCXML parent = root; if (parent == null) { parent = scxml; } String path; PathResolver nextpr = null; if (pr == null) { path = src; } else { path = pr.resolvePath(src); nextpr = pr.getResolver(src); } String[] fragments = path.split("#", 2); String location = fragments[0]; String fragment = null; if (fragments.length > 1) { fragment = fragments[1]; } Digester externalSrcDigester; if (fragment != null) { // Cannot pull in all targets just yet, i.e. null parent externalSrcDigester = newInstance(null, nextpr, customActions); } else { externalSrcDigester = newInstance(parent, nextpr, customActions); } SCXML externalSCXML = null; try { externalSCXML = (SCXML) externalSrcDigester.parse(location); } catch (Exception e) { MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC); String errMsg = msgFormat.format(new Object[] { path }); throw new ModelException(errMsg + " : " + e.getMessage(), e); } // 2) Adopt the children and datamodel if (externalSCXML == null) { return; } State s = (State) digester.peek(); if (fragment == null) { // All targets pulled in since its not a src fragment Initial ini = new Initial(); Transition t = new Transition(); t.setNext(externalSCXML.getInitial()); ini.setTransition(t); s.setInitial(ini); Collection<TransitionTarget> children = externalSCXML.getChildren().values(); for (TransitionTarget child : children) { s.addChild(child); } s.setDatamodel(externalSCXML.getDatamodel()); } else { // Need to pull in descendent targets Object source = externalSCXML.getTargets().get(fragment); if (source == null) { MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT); String errMsg = msgFormat.format(new Object[] { path }); throw new ModelException(errMsg); } if (source instanceof State) { State include = (State) source; s.setOnEntry(include.getOnEntry()); s.setOnExit(include.getOnExit()); s.setDatamodel(include.getDatamodel()); List<History> histories = include.getHistory(); for (History h : histories) { s.addHistory(h); parent.addTarget(h); } Collection<TransitionTarget> children = include.getChildren().values(); for (TransitionTarget child : children) { s.addChild(child); parent.addTarget(child); addTargets(parent, child); } s.setInvoke(include.getInvoke()); s.setFinal(include.isFinal()); if (include.getInitial() != null) { s.setInitial(include.getInitial()); } List<Transition> transitions = include.getTransitionsList(); for (Transition t : transitions) { s.addTransition(t); } } else { MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET); String errMsg = msgFormat.format(new Object[] { path }); throw new ModelException(errMsg); } } } /** * Add all the nested targets from given target to given parent state machine. * * @param parent * The state machine * @param tt * The transition target to import */ private static void addTargets(final SCXML parent, final TransitionTarget tt) { List<History> histories = tt.getHistory(); for (History h : histories) { parent.addTarget(h); } if (tt instanceof State) { Collection<TransitionTarget> children = ((State) tt).getChildren().values(); for (TransitionTarget child : children) { parent.addTarget(child); addTargets(parent, child); } } else if (tt instanceof Parallel) { Collection<TransitionTarget> children = ((Parallel) tt).getChildren(); for (TransitionTarget child : children) { parent.addTarget(child); addTargets(parent, child); } } } } /** * Custom digestion rule for setting PathResolver for runtime retrieval. */ private static class SetPathResolverRule extends Rule { /** * The PathResolver to set. * * @see PathResolver */ private final PathResolver pr; /** * Constructor. * * @param pr * The PathResolver * * @see PathResolver */ SetPathResolverRule(final PathResolver pr) { super(); this.pr = pr; } /** * @see Rule#begin(String, String, Attributes) */ @Override public final void begin(final String namespace, final String name, final Attributes attributes) { PathResolverHolder prHolder = (PathResolverHolder) getDigester().peek(); prHolder.setPathResolver(pr); } } /** * Custom digestion rule for setting state parent of finalize. */ private static class UpdateFinalizeRule extends Rule { /** * @see Rule#begin(String, String, Attributes) */ @Override public final void begin(final String namespace, final String name, final Attributes attributes) { Finalize finalize = (Finalize) getDigester().peek(); // state/invoke/finalize --> peek(2) TransitionTarget tt = (TransitionTarget) getDigester().peek(2); finalize.setParent(tt); } } /** * Custom digestion rule for attaching a snapshot of current namespaces to SCXML actions for deferred XPath evaluation. */ private static class SetCurrentNamespacesRule extends Rule { /** * @see Rule#begin(String, String, Attributes) */ @Override public final void begin(final String namespace, final String name, final Attributes attributes) { NamespacePrefixesHolder nsHolder = (NamespacePrefixesHolder) getDigester().peek(); nsHolder.setNamespaces(getDigester().getCurrentNamespaces()); } } /** * Custom digestion rule logging ignored elements. */ private static class IgnoredElementRule extends Rule { /** * @see Rule#begin(String, String, Attributes) */ @Override public final void begin(final String namespace, final String name, final Attributes attributes) { org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLParser.class); Locator l = getDigester().getDocumentLocator(); String identifier = l.getSystemId(); if (identifier == null) { identifier = l.getPublicId(); } StringBuffer sb = new StringBuffer(); sb.append("Ignoring element <").append(name).append("> in namespace \"").append(namespace) .append("\" at ").append(identifier).append(":").append(l.getLineNumber()).append(":") .append(l.getColumnNumber()).append(" and digester match \"").append(getDigester().getMatch()) .append("\""); log.warn(sb.toString()); } } /** * Custom digestion rule for saving the body content in the object model. */ private static class SetBodyRule extends Rule { /** * @see Rule#body(String, String, String) */ @Override public final void body(final String namespace, final String name, final String body) { BodyContainer bc = (BodyContainer) getDigester().peek(); bc.setBody(body); } } }