Coverage Report - org.apache.commons.scxml.io.SCXMLDigester

Classes in this File Line Coverage Branch Coverage Complexity
SCXMLDigester
76% 
78% 
1.918

 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.scxml.io;
 18  
 
 19  
 import java.io.IOException;
 20  
 import java.net.URL;
 21  
 import java.text.MessageFormat;
 22  
 import java.util.List;
 23  
 import java.util.Map;
 24  
 
 25  
 import javax.xml.parsers.DocumentBuilder;
 26  
 import javax.xml.parsers.DocumentBuilderFactory;
 27  
 import javax.xml.parsers.ParserConfigurationException;
 28  
 
 29  
 import org.apache.commons.digester.Digester;
 30  
 import org.apache.commons.digester.ExtendedBaseRules;
 31  
 import org.apache.commons.digester.NodeCreateRule;
 32  
 import org.apache.commons.digester.ObjectCreateRule;
 33  
 import org.apache.commons.digester.Rule;
 34  
 import org.apache.commons.digester.SetNextRule;
 35  
 import org.apache.commons.digester.SetPropertiesRule;
 36  
 import org.apache.commons.logging.LogFactory;
 37  
 
 38  
 import org.apache.commons.scxml.PathResolver;
 39  
 import org.apache.commons.scxml.SCXMLHelper;
 40  
 import org.apache.commons.scxml.env.URLResolver;
 41  
 import org.apache.commons.scxml.model.Action;
 42  
 import org.apache.commons.scxml.model.Assign;
 43  
 import org.apache.commons.scxml.model.Cancel;
 44  
 import org.apache.commons.scxml.model.CustomAction;
 45  
 import org.apache.commons.scxml.model.Data;
 46  
 import org.apache.commons.scxml.model.Datamodel;
 47  
 import org.apache.commons.scxml.model.Else;
 48  
 import org.apache.commons.scxml.model.ElseIf;
 49  
 import org.apache.commons.scxml.model.Executable;
 50  
 import org.apache.commons.scxml.model.Exit;
 51  
 import org.apache.commons.scxml.model.ExternalContent;
 52  
 import org.apache.commons.scxml.model.Finalize;
 53  
 import org.apache.commons.scxml.model.History;
 54  
 import org.apache.commons.scxml.model.If;
 55  
 import org.apache.commons.scxml.model.Initial;
 56  
 import org.apache.commons.scxml.model.Invoke;
 57  
 import org.apache.commons.scxml.model.Log;
 58  
 import org.apache.commons.scxml.model.ModelException;
 59  
 import org.apache.commons.scxml.model.NamespacePrefixesHolder;
 60  
 import org.apache.commons.scxml.model.OnEntry;
 61  
 import org.apache.commons.scxml.model.OnExit;
 62  
 import org.apache.commons.scxml.model.Parallel;
 63  
 import org.apache.commons.scxml.model.Param;
 64  
 import org.apache.commons.scxml.model.PathResolverHolder;
 65  
 import org.apache.commons.scxml.model.SCXML;
 66  
 import org.apache.commons.scxml.model.Send;
 67  
 import org.apache.commons.scxml.model.State;
 68  
 import org.apache.commons.scxml.model.Transition;
 69  
 import org.apache.commons.scxml.model.TransitionTarget;
 70  
 import org.apache.commons.scxml.model.Var;
 71  
 
 72  
 import org.w3c.dom.Element;
 73  
 import org.w3c.dom.Node;
 74  
 import org.w3c.dom.NodeList;
 75  
 
 76  
 import org.xml.sax.Attributes;
 77  
 import org.xml.sax.ErrorHandler;
 78  
 import org.xml.sax.InputSource;
 79  
 import org.xml.sax.SAXException;
 80  
 
 81  
 /**
 82  
  * <p>The SCXMLDigester provides the ability to digest a SCXML document into
 83  
  * the Java object model provided in the model package.</p>
 84  
  * <p>The SCXMLDigester can be used for:</p>
 85  
  * <ol>
 86  
  *  <li>Digest a SCXML file into the Commons SCXML Java object model.</li>
 87  
  *  <li>Obtain a SCXML Digester for further customization of the default
 88  
  *      ruleset.</li>
 89  
  * </ol>
 90  
  */
 91  
 public final class SCXMLDigester {
 92  
 
 93  
     /**
 94  
      * The SCXML namespace that this Digester is built for. Any document
 95  
      * that is intended to be parsed by this digester <b>must</b>
 96  
      * bind the SCXML elements to this namespace.
 97  
      */
 98  
     private static final String NAMESPACE_SCXML =
 99  
         "http://www.w3.org/2005/07/scxml";
 100  
 
 101  
     //---------------------- PUBLIC METHODS ----------------------//
 102  
     /**
 103  
      * <p>API for standalone usage where the SCXML document is a URL.</p>
 104  
      *
 105  
      * @param scxmlURL
 106  
      *            a canonical absolute URL to parse (relative URLs within the
 107  
      *            top level document are to be resovled against this URL).
 108  
      * @param errHandler
 109  
      *            The SAX ErrorHandler
 110  
      *
 111  
      * @return SCXML The SCXML object corresponding to the file argument
 112  
      *
 113  
      * @throws IOException Underlying Digester parsing threw an IOException
 114  
      * @throws SAXException Underlying Digester parsing threw a SAXException
 115  
      * @throws ModelException If the resulting document model has flaws
 116  
      *
 117  
      * @see ErrorHandler
 118  
      * @see PathResolver
 119  
      */
 120  
     public static SCXML digest(final URL scxmlURL,
 121  
             final ErrorHandler errHandler)
 122  
     throws IOException, SAXException, ModelException {
 123  
 
 124  2
         if (scxmlURL == null) {
 125  0
             throw new IllegalArgumentException(ERR_NULL_URL);
 126  
         }
 127  
 
 128  2
         return digest(scxmlURL, errHandler, null);
 129  
 
 130  
     }
 131  
 
 132  
     /**
 133  
      * <p>API for standalone usage where the SCXML document is a URI.
 134  
      * A PathResolver must be provided.</p>
 135  
      *
 136  
      * @param pathResolver
 137  
      *            The PathResolver for this context
 138  
      * @param documentRealPath
 139  
      *            The String pointing to the absolute (real) path of the
 140  
      *            SCXML document
 141  
      * @param errHandler
 142  
      *            The SAX ErrorHandler
 143  
      *
 144  
      * @return SCXML The SCXML object corresponding to the file argument
 145  
      *
 146  
      * @throws IOException Underlying Digester parsing threw an IOException
 147  
      * @throws SAXException Underlying Digester parsing threw a SAXException
 148  
      * @throws ModelException If the resulting document model has flaws
 149  
      *
 150  
      * @see ErrorHandler
 151  
      * @see PathResolver
 152  
      */
 153  
     public static SCXML digest(final String documentRealPath,
 154  
             final ErrorHandler errHandler, final PathResolver pathResolver)
 155  
     throws IOException, SAXException, ModelException {
 156  
 
 157  1
         return digest(documentRealPath, errHandler, pathResolver, null);
 158  
 
 159  
     }
 160  
 
 161  
     /**
 162  
      * <p>API for standalone usage where the SCXML document is an
 163  
      * InputSource. This method may be used when the SCXML document is
 164  
      * packaged in a Java archive, or part of a compound document
 165  
      * where the SCXML root is available as a
 166  
      * <code>org.w3c.dom.Element</code> or via a <code>java.io.Reader</code>.
 167  
      * </p>
 168  
      *
 169  
      * <p><em>Note:</em> Since there is no path resolution, the SCXML document
 170  
      * must not have external state sources.</p>
 171  
      *
 172  
      * @param documentInputSource
 173  
      *            The InputSource for the SCXML document
 174  
      * @param errHandler
 175  
      *            The SAX ErrorHandler
 176  
      *
 177  
      * @return SCXML The SCXML object corresponding to the file argument
 178  
      *
 179  
      * @throws IOException Underlying Digester parsing threw an IOException
 180  
      * @throws SAXException Underlying Digester parsing threw a SAXException
 181  
      * @throws ModelException If the resulting document model has flaws
 182  
      *
 183  
      * @see ErrorHandler
 184  
      */
 185  
     public static SCXML digest(final InputSource documentInputSource,
 186  
             final ErrorHandler errHandler)
 187  
     throws IOException, SAXException, ModelException {
 188  
 
 189  0
         if (documentInputSource == null) {
 190  0
             throw new IllegalArgumentException(ERR_NULL_ISRC);
 191  
         }
 192  
 
 193  0
         return digest(documentInputSource, errHandler, null);
 194  
 
 195  
     }
 196  
 
 197  
     /**
 198  
      * <p>API for standalone usage where the SCXML document is a URL, and
 199  
      * the document uses custom actions.</p>
 200  
      *
 201  
      * @param scxmlURL
 202  
      *            a canonical absolute URL to parse (relative URLs within the
 203  
      *            top level document are to be resovled against this URL).
 204  
      * @param errHandler
 205  
      *            The SAX ErrorHandler
 206  
      * @param customActions
 207  
      *            The list of {@link CustomAction}s this digester
 208  
      *            instance will process, can be null or empty
 209  
      *
 210  
      * @return SCXML The SCXML object corresponding to the file argument
 211  
      *
 212  
      * @throws IOException Underlying Digester parsing threw an IOException
 213  
      * @throws SAXException Underlying Digester parsing threw a SAXException
 214  
      * @throws ModelException If the resulting document model has flaws
 215  
      *
 216  
      * @see ErrorHandler
 217  
      * @see PathResolver
 218  
      */
 219  
     public static SCXML digest(final URL scxmlURL,
 220  
             final ErrorHandler errHandler, final List customActions)
 221  
     throws IOException, SAXException, ModelException {
 222  
 
 223  50
         SCXML scxml = null;
 224  50
         Digester scxmlDigester = SCXMLDigester
 225  
                 .newInstance(null, new URLResolver(scxmlURL), customActions);
 226  50
         scxmlDigester.setErrorHandler(errHandler);
 227  
 
 228  
         try {
 229  50
             scxml = (SCXML) scxmlDigester.parse(scxmlURL.toString());
 230  0
         } catch (RuntimeException rte) {
 231  
             // Intercept runtime exceptions, only to log them with a
 232  
             // sensible error message about failure in document parsing
 233  0
             MessageFormat msgFormat = new MessageFormat(ERR_DOC_PARSE_FAIL);
 234  0
             String errMsg = msgFormat.format(new Object[] {
 235  
                 String.valueOf(scxmlURL), rte.getMessage()
 236  
             });
 237  0
             org.apache.commons.logging.Log log = LogFactory.
 238  133
                 getLog(SCXMLDigester.class);
 239  0
             log.error(errMsg, rte);
 240  0
             throw rte;
 241  50
         }
 242  
 
 243  50
         if (scxml != null) {
 244  50
             ModelUpdater.updateSCXML(scxml);
 245  
         }
 246  
 
 247  50
         return scxml;
 248  
 
 249  
     }
 250  
 
 251  
     /**
 252  
      * <p>API for standalone usage where the SCXML document is a URI.
 253  
      * A PathResolver must be provided.</p>
 254  
      *
 255  
      * @param pathResolver
 256  
      *            The PathResolver for this context
 257  
      * @param documentRealPath
 258  
      *            The String pointing to the absolute (real) path of the
 259  
      *            SCXML document
 260  
      * @param errHandler
 261  
      *            The SAX ErrorHandler
 262  
      * @param customActions
 263  
      *            The list of {@link CustomAction}s this digester
 264  
      *            instance will process, can be null or empty
 265  
      *
 266  
      * @return SCXML The SCXML object corresponding to the file argument
 267  
      *
 268  
      * @throws IOException Underlying Digester parsing threw an IOException
 269  
      * @throws SAXException Underlying Digester parsing threw a SAXException
 270  
      * @throws ModelException If the resulting document model has flaws
 271  
      *
 272  
      * @see ErrorHandler
 273  
      * @see PathResolver
 274  
      */
 275  
     public static SCXML digest(final String documentRealPath,
 276  
             final ErrorHandler errHandler, final PathResolver pathResolver,
 277  
             final List customActions)
 278  
     throws IOException, SAXException, ModelException {
 279  
 
 280  1
         if (documentRealPath == null) {
 281  0
             throw new IllegalArgumentException(ERR_NULL_PATH);
 282  
         }
 283  
 
 284  1
         SCXML scxml = null;
 285  1
         Digester scxmlDigester = SCXMLDigester.newInstance(null, pathResolver,
 286  
             customActions);
 287  1
         scxmlDigester.setErrorHandler(errHandler);
 288  
 
 289  
         try {
 290  1
             scxml = (SCXML) scxmlDigester.parse(documentRealPath);
 291  0
         } catch (RuntimeException rte) {
 292  
             // Intercept runtime exceptions, only to log them with a
 293  
             // sensible error message about failure in document parsing
 294  0
             MessageFormat msgFormat = new MessageFormat(ERR_DOC_PARSE_FAIL);
 295  0
             String errMsg = msgFormat.format(new Object[] {
 296  
                 documentRealPath, rte.getMessage()
 297  
             });
 298  0
             org.apache.commons.logging.Log log = LogFactory.
 299  
                 getLog(SCXMLDigester.class);
 300  0
             log.error(errMsg, rte);
 301  0
             throw rte;
 302  1
         }
 303  
 
 304  1
         if (scxml != null) {
 305  1
             ModelUpdater.updateSCXML(scxml);
 306  
         }
 307  
 
 308  1
         return scxml;
 309  
 
 310  
     }
 311  
 
 312  
     /**
 313  
      * <p>API for standalone usage where the SCXML document is an
 314  
      * InputSource. This method may be used when the SCXML document is
 315  
      * packaged in a Java archive, or part of a compound document
 316  
      * where the SCXML root is available as a
 317  
      * <code>org.w3c.dom.Element</code> or via a <code>java.io.Reader</code>.
 318  
      * </p>
 319  
      *
 320  
      * <p><em>Note:</em> Since there is no path resolution, the SCXML document
 321  
      * must not have external state sources.</p>
 322  
      *
 323  
      * @param documentInputSource
 324  
      *            The InputSource for the SCXML document
 325  
      * @param errHandler
 326  
      *            The SAX ErrorHandler
 327  
      * @param customActions
 328  
      *            The list of {@link CustomAction}s this digester
 329  
      *            instance will process, can be null or empty
 330  
      *
 331  
      * @return SCXML The SCXML object corresponding to the file argument
 332  
      *
 333  
      * @throws IOException Underlying Digester parsing threw an IOException
 334  
      * @throws SAXException Underlying Digester parsing threw a SAXException
 335  
      * @throws ModelException If the resulting document model has flaws
 336  
      *
 337  
      * @see ErrorHandler
 338  
      */
 339  
     public static SCXML digest(final InputSource documentInputSource,
 340  
             final ErrorHandler errHandler, final List customActions)
 341  
     throws IOException, SAXException, ModelException {
 342  
 
 343  0
         Digester scxmlDigester = SCXMLDigester.newInstance(null, null,
 344  
             customActions);
 345  0
         scxmlDigester.setErrorHandler(errHandler);
 346  
 
 347  0
         SCXML scxml = null;
 348  
         try {
 349  0
             scxml = (SCXML) scxmlDigester.parse(documentInputSource);
 350  0
         }  catch (RuntimeException rte) {
 351  
             // Intercept runtime exceptions, only to log them with a
 352  
             // sensible error message about failure in document parsing
 353  0
             org.apache.commons.logging.Log log = LogFactory.
 354  
                 getLog(SCXMLDigester.class);
 355  0
             log.error(ERR_ISRC_PARSE_FAIL, rte);
 356  0
             throw rte;
 357  0
         }
 358  
 
 359  0
         if (scxml != null) {
 360  0
             ModelUpdater.updateSCXML(scxml);
 361  
         }
 362  
 
 363  0
         return scxml;
 364  
 
 365  
     }
 366  
 
 367  
     /**
 368  
      * <p>Obtain a SCXML digester instance for further customization.</p>
 369  
      * <b>API Notes:</b>
 370  
      * <ul>
 371  
      *   <li>Use the digest() convenience methods if you do not
 372  
      *       need a custom digester.</li>
 373  
      *   <li>After the SCXML document is parsed by the customized digester,
 374  
      *       the object model <b>must</b> be made executor-ready by calling
 375  
      *       <code>updateSCXML(SCXML)</code> method in this class.</li>
 376  
      * </ul>
 377  
      *
 378  
      * @return Digester A newly configured SCXML digester instance
 379  
      *
 380  
      * @see SCXMLDigester#updateSCXML(SCXML)
 381  
      */
 382  
     public static Digester newInstance() {
 383  
 
 384  0
         return newInstance(null, null, null);
 385  
 
 386  
     }
 387  
 
 388  
     /**
 389  
      * <p>Obtain a SCXML digester instance for further customization.</p>
 390  
      * <b>API Notes:</b>
 391  
      * <ul>
 392  
      *   <li>Use the digest() convenience methods if you do not
 393  
      *       need a custom digester.</li>
 394  
      *   <li>After the SCXML document is parsed by the customized digester,
 395  
      *       the object model <b>must</b> be made executor-ready by calling
 396  
      *       <code>updateSCXML(SCXML)</code> method in this class.</li>
 397  
      * </ul>
 398  
      *
 399  
      * @param pr The PathResolver, may be null for standalone documents
 400  
      * @return Digester A newly configured SCXML digester instance
 401  
      *
 402  
      * @see SCXMLDigester#updateSCXML(SCXML)
 403  
      */
 404  
     public static Digester newInstance(final PathResolver pr) {
 405  
 
 406  0
         return newInstance(null, pr, null);
 407  
 
 408  
     }
 409  
 
 410  
     /**
 411  
      * <p>Obtain a SCXML digester instance for further customization.</p>
 412  
      * <b>API Notes:</b>
 413  
      * <ul>
 414  
      *   <li>Use the digest() convenience methods if you do not
 415  
      *       need a custom digester.</li>
 416  
      *   <li>After the SCXML document is parsed by the customized digester,
 417  
      *       the object model <b>must</b> be made executor-ready by calling
 418  
      *       <code>updateSCXML(SCXML)</code> method in this class.</li>
 419  
      * </ul>
 420  
      *
 421  
      * @param scxml The parent SCXML document if there is one (in case of
 422  
      *              state templates for example), null otherwise
 423  
      * @param pr The PathResolver, may be null for standalone documents
 424  
      * @return Digester A newly configured SCXML digester instance
 425  
      *
 426  
      * @see SCXMLDigester#updateSCXML(SCXML)
 427  
      */
 428  
     public static Digester newInstance(final SCXML scxml,
 429  
             final PathResolver pr) {
 430  
 
 431  0
         return newInstance(scxml, pr, null);
 432  
 
 433  
     }
 434  
 
 435  
     /**
 436  
      * <p>Obtain a SCXML digester instance for further customization.</p>
 437  
      * <b>API Notes:</b>
 438  
      * <ul>
 439  
      *   <li>Use the digest() convenience methods if you do not
 440  
      *       need a custom digester.</li>
 441  
      *   <li>After the SCXML document is parsed by the customized digester,
 442  
      *       the object model <b>must</b> be made executor-ready by calling
 443  
      *       <code>updateSCXML(SCXML)</code> method in this class.</li>
 444  
      * </ul>
 445  
      *
 446  
      * @param scxml The parent SCXML document if there is one (in case of
 447  
      *              state templates for example), null otherwise
 448  
      * @param pr The PathResolver, may be null for standalone documents
 449  
      * @param customActions The list of {@link CustomAction}s this digester
 450  
      *              instance will process, can be null or empty
 451  
      * @return Digester A newly configured SCXML digester instance
 452  
      *
 453  
      * @see SCXMLDigester#updateSCXML(SCXML)
 454  
      */
 455  
     public static Digester newInstance(final SCXML scxml,
 456  
             final PathResolver pr, final List customActions) {
 457  
 
 458  54
         Digester digester = new Digester();
 459  54
         digester.setNamespaceAware(true);
 460  
         //Uncomment next line after SCXML DTD is available
 461  
         //digester.setValidating(true);
 462  54
         digester.setRules(initRules(scxml, pr, customActions));
 463  54
         return digester;
 464  
     }
 465  
 
 466  
     /**
 467  
      * <p>Update the SCXML object model and make it SCXMLExecutor ready.
 468  
      * This is part of post-digester processing, and sets up the necessary
 469  
      * object references throughtout the SCXML object model for the parsed
 470  
      * document. Should be used only if a customized digester obtained
 471  
      * using the <code>newInstance()</code> methods is needed.</p>
 472  
      *
 473  
      * @param scxml The SCXML object (output from Digester)
 474  
      * @throws ModelException If the document model has flaws
 475  
      */
 476  
    public static void updateSCXML(final SCXML scxml)
 477  
    throws ModelException {
 478  0
        ModelUpdater.updateSCXML(scxml);
 479  0
    }
 480  
 
 481  
     //---------------------- PRIVATE CONSTANTS ----------------------//
 482  
     //// Patterns to get the digestion going, prefixed by XP_
 483  
     /** Root &lt;scxml&gt; element. */
 484  
     private static final String XP_SM = "scxml";
 485  
 
 486  
     /** &lt;state&gt; children of root &lt;scxml&gt; element. */
 487  
     private static final String XP_SM_ST = "scxml/state";
 488  
 
 489  
     //// Universal matches, prefixed by XPU_
 490  
     // State
 491  
     /** &lt;state&gt; children of &lt;state&gt; elements. */
 492  
     private static final String XPU_ST_ST = "!*/state/state";
 493  
 
 494  
     /** &lt;state&gt; children of &lt;parallel&gt; elements. */
 495  
     private static final String XPU_PAR_ST = "!*/parallel/state";
 496  
 
 497  
     /** &lt;state&gt; children of transition &lt;target&gt; elements. */
 498  
     private static final String XPU_TR_TAR_ST = "!*/transition/target/state";
 499  
 
 500  
     //private static final String XPU_ST_TAR_ST = "!*/state/target/state";
 501  
 
 502  
     // Parallel
 503  
     /** &lt;parallel&gt; child of &lt;state&gt; elements. */
 504  
     private static final String XPU_ST_PAR = "!*/state/parallel";
 505  
 
 506  
     // If
 507  
     /** &lt;if&gt; element. */
 508  
     private static final String XPU_IF = "!*/if";
 509  
 
 510  
     // Executables, next three patterns useful when adding custom actions
 511  
     /** &lt;onentry&gt; element. */
 512  
     private static final String XPU_ONEN = "!*/onentry";
 513  
 
 514  
     /** &lt;onexit&gt; element. */
 515  
     private static final String XPU_ONEX = "!*/onexit";
 516  
 
 517  
     /** &lt;transition&gt; element. */
 518  
     private static final String XPU_TR = "!*/transition";
 519  
 
 520  
     /** &lt;finalize&gt; element. */
 521  
     private static final String XPU_FIN = "!*/finalize";
 522  
 
 523  
     //// Path Fragments, constants prefixed by XPF_
 524  
     // Onentries and Onexits
 525  
     /** &lt;onentry&gt; child element. */
 526  
     private static final String XPF_ONEN = "/onentry";
 527  
 
 528  
     /** &lt;onexit&gt; child element. */
 529  
     private static final String XPF_ONEX = "/onexit";
 530  
 
 531  
     // Datamodel section
 532  
     /** &lt;datamodel&gt; child element. */
 533  
     private static final String XPF_DM = "/datamodel";
 534  
 
 535  
     /** Individual &lt;data&gt; elements. */
 536  
     private static final String XPF_DATA = "/data";
 537  
 
 538  
     // Initial
 539  
     /** &lt;initial&gt; child element. */
 540  
     private static final String XPF_INI = "/initial";
 541  
 
 542  
     // Invoke, param and finalize
 543  
     /** &lt;invoke&gt; child element of &lt;state&gt;. */
 544  
     private static final String XPF_INV = "/invoke";
 545  
 
 546  
     /** &lt;param&gt; child element of &lt;invoke&gt;. */
 547  
     private static final String XPF_PRM = "/param";
 548  
 
 549  
     /** &lt;finalize&gt; child element of &lt;invoke&gt;. */
 550  
     private static final String XPF_FIN = "/finalize";
 551  
 
 552  
     // History
 553  
     /** &lt;history&gt; child element. */
 554  
     private static final String XPF_HIST = "/history";
 555  
 
 556  
     // Transition, target and exit
 557  
     /** &lt;transition&gt; child element. */
 558  
     private static final String XPF_TR = "/transition";
 559  
 
 560  
     /** &lt;target&gt; child element. */
 561  
     private static final String XPF_TAR = "/target";
 562  
 
 563  
     /** &lt;exit&gt; child element. */
 564  
     private static final String XPF_EXT = "/exit";
 565  
 
 566  
     // Actions
 567  
     /** &lt;var&gt; child element. */
 568  
     private static final String XPF_VAR = "/var";
 569  
 
 570  
     /** &lt;assign&gt; child element. */
 571  
     private static final String XPF_ASN = "/assign";
 572  
 
 573  
     /** &lt;log&gt; child element. */
 574  
     private static final String XPF_LOG = "/log";
 575  
 
 576  
     /** &lt;send&gt; child element. */
 577  
     private static final String XPF_SND = "/send";
 578  
 
 579  
     /** &lt;cancel&gt; child element. */
 580  
     private static final String XPF_CAN = "/cancel";
 581  
 
 582  
     /** &lt;elseif&gt; child element. */
 583  
     private static final String XPF_EIF = "/elseif";
 584  
 
 585  
     /** &lt;else&gt; child element. */
 586  
     private static final String XPF_ELS = "/else";
 587  
 
 588  
     //// Other constants
 589  
     // Error messages
 590  
     /**
 591  
      * Null URL passed as argument.
 592  
      */
 593  
     private static final String ERR_NULL_URL = "Cannot parse null URL";
 594  
 
 595  
     /**
 596  
      * Null path passed as argument.
 597  
      */
 598  
     private static final String ERR_NULL_PATH = "Cannot parse null URL";
 599  
 
 600  
     /**
 601  
      * Null InputSource passed as argument.
 602  
      */
 603  
     private static final String ERR_NULL_ISRC = "Cannot parse null URL";
 604  
 
 605  
     /**
 606  
      * Parsing SCXML document has failed.
 607  
      */
 608  
     private static final String ERR_DOC_PARSE_FAIL = "Error parsing "
 609  
         + "SCXML document: \"{0}\", with message: \"{1}\"\n";
 610  
 
 611  
     /**
 612  
      * Parsing SCXML document InputSource has failed.
 613  
      */
 614  
     private static final String ERR_ISRC_PARSE_FAIL =
 615  
         "Could not parse SCXML InputSource";
 616  
 
 617  
     /**
 618  
      * Parser configuration error while registering data rule.
 619  
      */
 620  
     private static final String ERR_PARSER_CFG_DATA = "XML Parser "
 621  
         + "misconfiguration, error registering <data> element rule";
 622  
 
 623  
     /**
 624  
      * Parser configuration error while registering send rule.
 625  
      */
 626  
     private static final String ERR_PARSER_CFG_SEND = "XML Parser "
 627  
         + "misconfiguration, error registering <send> element rule";
 628  
 
 629  
     /**
 630  
      * Parser configuration error while registering body content rule for
 631  
      * custom action.
 632  
      */
 633  
     private static final String ERR_PARSER_CFG_CUSTOM = "XML Parser "
 634  
         + "misconfiguration, error registering custom action rules";
 635  
 
 636  
     /**
 637  
      * Error message while attempting to define a custom action which does
 638  
      * not extend the Commons SCXML Action base class.
 639  
      */
 640  
     private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list"
 641  
         + " contained unknown object (not a Commons SCXML Action subtype)";
 642  
 
 643  
     // String constants
 644  
     /** Slash. */
 645  
     private static final String STR_SLASH = "/";
 646  
 
 647  
     //---------------------- PRIVATE UTILITY METHODS ----------------------//
 648  
     /*
 649  
      * Private utility functions for configuring digester rule base for SCXML.
 650  
      */
 651  
     /**
 652  
      * Initialize the Digester rules for the current document.
 653  
      *
 654  
      * @param scxml The parent SCXML document (or null)
 655  
      * @param pr The PathResolver
 656  
      * @param customActions The list of custom actions this digester needs
 657  
      *                      to be able to process
 658  
      *
 659  
      * @return scxmlRules The rule set to be used for digestion
 660  
      */
 661  
     private static ExtendedBaseRules initRules(final SCXML scxml,
 662  
             final PathResolver pr, final List customActions) {
 663  
 
 664  54
         ExtendedBaseRules scxmlRules = new ExtendedBaseRules();
 665  54
         scxmlRules.setNamespaceURI(NAMESPACE_SCXML);
 666  
 
 667  
         //// SCXML
 668  54
         scxmlRules.add(XP_SM, new ObjectCreateRule(SCXML.class));
 669  54
         scxmlRules.add(XP_SM, new SetPropertiesRule());
 670  
 
 671  
         //// Datamodel at document root i.e. <scxml> datamodel
 672  54
         addDatamodelRules(XP_SM + XPF_DM, scxmlRules, scxml, pr);
 673  
 
 674  
         //// States
 675  
         // Level one states
 676  54
         addStateRules(XP_SM_ST, scxmlRules, customActions, scxml, pr, 0);
 677  54
         scxmlRules.add(XP_SM_ST, new SetNextRule("addState"));
 678  
         // Nested states
 679  54
         addStateRules(XPU_ST_ST, scxmlRules, customActions, scxml, pr, 1);
 680  54
         scxmlRules.add(XPU_ST_ST, new SetNextRule("addChild"));
 681  
 
 682  
         // Parallel states
 683  54
         addStateRules(XPU_PAR_ST, scxmlRules, customActions, scxml, pr, 1);
 684  54
         scxmlRules.add(XPU_PAR_ST, new SetNextRule("addState"));
 685  
         // Target states
 686  54
         addStateRules(XPU_TR_TAR_ST, scxmlRules, customActions, scxml, pr, 2);
 687  54
         scxmlRules.add(XPU_TR_TAR_ST, new SetNextRule("setTarget"));
 688  
 
 689  
         //// Parallels
 690  54
         addParallelRules(XPU_ST_PAR, scxmlRules, pr, customActions, scxml);
 691  
 
 692  
         //// Ifs
 693  54
         addIfRules(XPU_IF, scxmlRules, pr, customActions);
 694  
 
 695  
         //// Custom actions
 696  54
         addCustomActionRules(XPU_ONEN, scxmlRules, customActions);
 697  54
         addCustomActionRules(XPU_ONEX, scxmlRules, customActions);
 698  54
         addCustomActionRules(XPU_TR, scxmlRules, customActions);
 699  54
         addCustomActionRules(XPU_IF, scxmlRules, customActions);
 700  54
         addCustomActionRules(XPU_FIN, scxmlRules, customActions);
 701  
 
 702  54
         return scxmlRules;
 703  
 
 704  
     }
 705  
 
 706  
     /**
 707  
      * Add Digester rules for all &lt;state&gt; elements.
 708  
      *
 709  
      * @param xp The Digester style XPath expression of the parent
 710  
      *           XML element
 711  
      * @param scxmlRules The rule set to be used for digestion
 712  
      * @param customActions The list of custom actions this digester needs
 713  
      *                      to be able to process
 714  
      * @param scxml The parent SCXML document (or null)
 715  
      * @param pr The PathResolver
 716  
      * @param parent The distance between this state and its parent
 717  
      *               state on the Digester stack
 718  
      */
 719  
     private static void addStateRules(final String xp,
 720  
             final ExtendedBaseRules scxmlRules, final List customActions,
 721  
             final SCXML scxml, final PathResolver pr, final int parent) {
 722  216
         scxmlRules.add(xp, new ObjectCreateRule(State.class));
 723  216
         addStatePropertiesRules(xp, scxmlRules, customActions, pr, scxml);
 724  216
         addDatamodelRules(xp + XPF_DM, scxmlRules, scxml, pr);
 725  216
         addInvokeRules(xp + XPF_INV, scxmlRules, customActions, pr, scxml);
 726  216
         addInitialRules(xp + XPF_INI, scxmlRules, customActions, pr, scxml);
 727  216
         addHistoryRules(xp + XPF_HIST, scxmlRules, customActions, pr, scxml);
 728  216
         addParentRule(xp, scxmlRules, parent);
 729  216
         addTransitionRules(xp + XPF_TR, scxmlRules, "addTransition",
 730  
             pr, customActions);
 731  216
         addHandlerRules(xp, scxmlRules, pr, customActions);
 732  216
         scxmlRules.add(xp, new UpdateModelRule(scxml));
 733  216
     }
 734  
 
 735  
     /**
 736  
      * Add Digester rules for all &lt;parallel&gt; elements.
 737  
      *
 738  
      * @param xp The Digester style XPath expression of the parent
 739  
      *           XML element
 740  
      * @param scxmlRules The rule set to be used for digestion
 741  
      * @param customActions The list of custom actions this digester needs
 742  
      *                      to be able to process
 743  
      * @param pr The {@link PathResolver} for this document
 744  
      * @param scxml The parent SCXML document (or null)
 745  
      */
 746  
     private static void addParallelRules(final String xp,
 747  
             final ExtendedBaseRules scxmlRules, final PathResolver pr,
 748  
             final List customActions, final SCXML scxml) {
 749  54
         addSimpleRulesTuple(xp, scxmlRules, Parallel.class, null, null,
 750  
                 "setParallel");
 751  54
         addHandlerRules(xp, scxmlRules, pr, customActions);
 752  54
         addParentRule(xp, scxmlRules, 1);
 753  54
         scxmlRules.add(xp, new UpdateModelRule(scxml));
 754  54
     }
 755  
 
 756  
     /**
 757  
      * Add Digester rules for all &lt;state&gt; element attributes.
 758  
      *
 759  
      * @param xp The Digester style XPath expression of the parent
 760  
      *           XML element
 761  
      * @param scxmlRules The rule set to be used for digestion
 762  
      * @param customActions The list of custom actions this digester needs
 763  
      *                      to be able to process
 764  
      * @param pr The PathResolver
 765  
      * @param scxml The root document, if this one is src'ed in
 766  
      */
 767  
     private static void addStatePropertiesRules(final String xp,
 768  
             final ExtendedBaseRules scxmlRules, final List customActions,
 769  
             final PathResolver pr, final SCXML scxml) {
 770  216
         scxmlRules.add(xp, new SetPropertiesRule(new String[] {"id", "final"},
 771  
             new String[] {"id", "isFinal"}));
 772  216
         scxmlRules.add(xp, new DigestSrcAttributeRule(scxml,
 773  
             customActions, pr));
 774  216
     }
 775  
 
 776  
     /**
 777  
      * Add Digester rules for all &lt;datamodel&gt; elements.
 778  
      *
 779  
      * @param xp The Digester style XPath expression of the parent
 780  
      *           XML element
 781  
      * @param scxmlRules The rule set to be used for digestion
 782  
      * @param pr The PathResolver
 783  
      * @param scxml The parent SCXML document (or null)
 784  
      */
 785  
     private static void addDatamodelRules(final String xp,
 786  
             final ExtendedBaseRules scxmlRules, final SCXML scxml,
 787  
             final PathResolver pr) {
 788  270
         scxmlRules.add(xp, new ObjectCreateRule(Datamodel.class));
 789  270
         scxmlRules.add(xp + XPF_DATA, new ObjectCreateRule(Data.class));
 790  270
         scxmlRules.add(xp + XPF_DATA, new SetPropertiesRule());
 791  270
         scxmlRules.add(xp + XPF_DATA, new SetCurrentNamespacesRule());
 792  270
         scxmlRules.add(xp + XPF_DATA, new SetNextRule("addData"));
 793  
         try {
 794  270
             scxmlRules.add(xp + XPF_DATA, new ParseDataRule(pr));
 795  0
         } catch (ParserConfigurationException pce) {
 796  0
             org.apache.commons.logging.Log log = LogFactory.
 797  
                 getLog(SCXMLDigester.class);
 798  0
             log.error(ERR_PARSER_CFG_DATA, pce);
 799  270
         }
 800  270
         scxmlRules.add(xp, new SetNextRule("setDatamodel"));
 801  270
     }
 802  
 
 803  
     /**
 804  
      * Add Digester rules for all &lt;invoke&gt; elements.
 805  
      *
 806  
      * @param xp The Digester style XPath expression of the parent
 807  
      *           XML element
 808  
      * @param scxmlRules The rule set to be used for digestion
 809  
      * @param customActions The list of {@link CustomAction}s this digester
 810  
      *              instance will process, can be null or empty
 811  
      * @param pr The PathResolver
 812  
      * @param scxml The parent SCXML document (or null)
 813  
      */
 814  
     private static void addInvokeRules(final String xp,
 815  
             final ExtendedBaseRules scxmlRules, final List customActions,
 816  
             final PathResolver pr, final SCXML scxml) {
 817  216
         scxmlRules.add(xp, new ObjectCreateRule(Invoke.class));
 818  216
         scxmlRules.add(xp, new SetPropertiesRule());
 819  216
         scxmlRules.add(xp, new SetCurrentNamespacesRule());
 820  216
         scxmlRules.add(xp, new SetPathResolverRule(pr));
 821  216
         scxmlRules.add(xp + XPF_PRM, new ObjectCreateRule(Param.class));
 822  216
         scxmlRules.add(xp + XPF_PRM, new SetPropertiesRule());
 823  216
         scxmlRules.add(xp + XPF_PRM, new SetCurrentNamespacesRule());
 824  216
         scxmlRules.add(xp + XPF_PRM, new SetNextRule("addParam"));
 825  216
         scxmlRules.add(xp + XPF_FIN, new ObjectCreateRule(Finalize.class));
 826  216
         scxmlRules.add(xp + XPF_FIN, new UpdateFinalizeRule());
 827  216
         addActionRules(xp + XPF_FIN, scxmlRules, pr, customActions);
 828  216
         scxmlRules.add(xp + XPF_FIN, new SetNextRule("setFinalize"));
 829  216
         scxmlRules.add(xp, new SetNextRule("setInvoke"));
 830  216
     }
 831  
 
 832  
     /**
 833  
      * Add Digester rules for all &lt;initial&gt; elements.
 834  
      *
 835  
      * @param xp The Digester style XPath expression of the parent
 836  
      *           XML element
 837  
      * @param scxmlRules The rule set to be used for digestion
 838  
      * @param customActions The list of custom actions this digester needs
 839  
      *                      to be able to process
 840  
      * @param pr The PathResolver
 841  
      * @param scxml The parent SCXML document (or null)
 842  
      */
 843  
     private static void addInitialRules(final String xp,
 844  
             final ExtendedBaseRules scxmlRules, final List customActions,
 845  
             final PathResolver pr, final SCXML scxml) {
 846  216
         scxmlRules.add(xp, new ObjectCreateRule(Initial.class));
 847  216
         addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr,
 848  
             scxml);
 849  216
         scxmlRules.add(xp, new UpdateModelRule(scxml));
 850  216
         addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition",
 851  
             pr, customActions);
 852  216
         scxmlRules.add(xp, new SetNextRule("setInitial"));
 853  216
     }
 854  
 
 855  
     /**
 856  
      * Add Digester rules for all &lt;history&gt; elements.
 857  
      *
 858  
      * @param xp The Digester style XPath expression of the parent
 859  
      *           XML element
 860  
      * @param scxmlRules The rule set to be used for digestion
 861  
      * @param customActions The list of custom actions this digester needs
 862  
      *                      to be able to process
 863  
      * @param pr The PathResolver
 864  
      * @param scxml The parent SCXML document (or null)
 865  
      */
 866  
     private static void addHistoryRules(final String xp,
 867  
             final ExtendedBaseRules scxmlRules, final List customActions,
 868  
             final PathResolver pr, final SCXML scxml) {
 869  216
         scxmlRules.add(xp, new ObjectCreateRule(History.class));
 870  216
         addPseudoStatePropertiesRules(xp, scxmlRules, customActions, pr,
 871  
             scxml);
 872  216
         scxmlRules.add(xp, new UpdateModelRule(scxml));
 873  216
         scxmlRules.add(xp, new SetPropertiesRule(new String[] {"type"},
 874  
             new String[] {"type"}));
 875  216
         addTransitionRules(xp + XPF_TR, scxmlRules, "setTransition",
 876  
             pr, customActions);
 877  216
         scxmlRules.add(xp, new SetNextRule("addHistory"));
 878  216
     }
 879  
 
 880  
     /**
 881  
      * Add Digester rules for all pseudo state (initial, history) element
 882  
      * attributes.
 883  
      *
 884  
      * @param xp The Digester style XPath expression of the parent
 885  
      *           XML element
 886  
      * @param scxmlRules The rule set to be used for digestion
 887  
      * @param customActions The list of custom actions this digester needs
 888  
      *                      to be able to process
 889  
      * @param pr The PathResolver
 890  
      * @param scxml The root document, if this one is src'ed in
 891  
      */
 892  
     private static void addPseudoStatePropertiesRules(final String xp,
 893  
             final ExtendedBaseRules scxmlRules, final List customActions,
 894  
             final PathResolver pr, final SCXML scxml) {
 895  432
         scxmlRules.add(xp, new SetPropertiesRule(new String[] {"id"},
 896  
             new String[] {"id"}));
 897  432
         scxmlRules.add(xp, new DigestSrcAttributeRule(scxml, customActions,
 898  
             pr));
 899  432
         addParentRule(xp, scxmlRules, 1);
 900  432
     }
 901  
 
 902  
     /**
 903  
      * Add Digester rule for all setting parent state.
 904  
      *
 905  
      * @param xp The Digester style XPath expression of the parent
 906  
      *           XML element
 907  
      * @param scxmlRules The rule set to be used for digestion
 908  
      * @param parent The distance between this state and its parent
 909  
      *               state on the Digester stack
 910  
      */
 911  
     private static void addParentRule(final String xp,
 912  
             final ExtendedBaseRules scxmlRules, final int parent) {
 913  702
         if (parent < 1) {
 914  54
             return;
 915  
         }
 916  648
         scxmlRules.add(xp, new Rule() {
 917  
             // A generic version of setTopRule
 918  648
             public void body(final String namespace, final String name,
 919  
                     final String text) throws Exception {
 920  153
                 TransitionTarget t = (TransitionTarget) getDigester().peek();
 921  153
                 TransitionTarget p = (TransitionTarget) getDigester().peek(
 922  
                         parent);
 923  
                 // CHANGE - Moved parent property to TransitionTarget
 924  153
                 t.setParent(p);
 925  153
             }
 926  
         });
 927  648
     }
 928  
 
 929  
     /**
 930  
      * Add Digester rules for all &lt;transition&gt; elements.
 931  
      *
 932  
      * @param xp The Digester style XPath expression of the parent
 933  
      *           XML element
 934  
      * @param scxmlRules The rule set to be used for digestion
 935  
      * @param setNextMethod The method name for adding this transition
 936  
      *             to its parent (defined by the SCXML Java object model).
 937  
      * @param pr The {@link PathResolver} for this document
 938  
      * @param customActions The list of custom actions this digester needs
 939  
      *                      to be able to process
 940  
      */
 941  
     private static void addTransitionRules(final String xp,
 942  
             final ExtendedBaseRules scxmlRules, final String setNextMethod,
 943  
             final PathResolver pr, final List customActions) {
 944  648
         scxmlRules.add(xp, new ObjectCreateRule(Transition.class));
 945  648
         scxmlRules.add(xp, new SetPropertiesRule(
 946  
             new String[] {"event", "cond", "target"},
 947  
             new String[] {"event", "cond", "next"}));
 948  648
         scxmlRules.add(xp, new SetCurrentNamespacesRule());
 949  648
         scxmlRules.add(xp + XPF_TAR, new SetPropertiesRule());
 950  648
         addActionRules(xp, scxmlRules, pr, customActions);
 951  648
         scxmlRules.add(xp + XPF_EXT, new Rule() {
 952  648
             public void end(final String namespace, final String name) {
 953  0
                 Transition t = (Transition) getDigester().peek(1);
 954  0
                 State exitState = new State();
 955  0
                 exitState.setIsFinal(true);
 956  0
                 t.setTarget(exitState);
 957  0
             }
 958  
         });
 959  648
         scxmlRules.add(xp, new SetNextRule(setNextMethod));
 960  648
     }
 961  
 
 962  
     /**
 963  
      * Add Digester rules for all &lt;onentry&gt; and &lt;onexit&gt;
 964  
      * elements.
 965  
      *
 966  
      * @param xp The Digester style XPath expression of the parent
 967  
      *           XML element
 968  
      * @param scxmlRules The rule set to be used for digestion
 969  
      * @param pr The {@link PathResolver} for this document
 970  
      * @param customActions The list of custom actions this digester needs
 971  
      *                      to be able to process
 972  
      */
 973  
     private static void addHandlerRules(final String xp,
 974  
             final ExtendedBaseRules scxmlRules, final PathResolver pr,
 975  
             final List customActions) {
 976  270
         scxmlRules.add(xp + XPF_ONEN, new ObjectCreateRule(OnEntry.class));
 977  270
         addActionRules(xp + XPF_ONEN, scxmlRules, pr, customActions);
 978  270
         scxmlRules.add(xp + XPF_ONEN, new SetNextRule("setOnEntry"));
 979  270
         scxmlRules.add(xp + XPF_ONEX, new ObjectCreateRule(OnExit.class));
 980  270
         addActionRules(xp + XPF_ONEX, scxmlRules, pr, customActions);
 981  270
         scxmlRules.add(xp + XPF_ONEX, new SetNextRule("setOnExit"));
 982  270
     }
 983  
 
 984  
     /**
 985  
      * Add Digester rules for all actions (&quot;executable&quot; elements).
 986  
      *
 987  
      * @param xp The Digester style XPath expression of the parent
 988  
      *           XML element
 989  
      * @param scxmlRules The rule set to be used for digestion
 990  
      * @param pr The {@link PathResolver} for this document
 991  
      * @param customActions The list of custom actions this digester needs
 992  
      *                      to be able to process
 993  
      */
 994  
     private static void addActionRules(final String xp,
 995  
             final ExtendedBaseRules scxmlRules, final PathResolver pr,
 996  
             final List customActions) {
 997  1458
         addActionRulesTuple(xp + XPF_ASN, scxmlRules, Assign.class);
 998  1458
         scxmlRules.add(xp + XPF_ASN, new SetPathResolverRule(pr));
 999  1458
         addActionRulesTuple(xp + XPF_VAR, scxmlRules, Var.class);
 1000  1458
         addActionRulesTuple(xp + XPF_LOG, scxmlRules, Log.class);
 1001  1458
         addSendRulesTuple(xp + XPF_SND, scxmlRules);
 1002  1458
         addActionRulesTuple(xp + XPF_CAN, scxmlRules, Cancel.class);
 1003  1458
         addActionRulesTuple(xp + XPF_EXT, scxmlRules, Exit.class);
 1004  
         //addCustomActionRules(xp, scxmlRules, customActions);
 1005  1458
     }
 1006  
 
 1007  
     /**
 1008  
      * Add custom action rules, if any custom actions are provided.
 1009  
      *
 1010  
      * @param xp The Digester style XPath expression of the parent
 1011  
      *           XML element
 1012  
      * @param scxmlRules The rule set to be used for digestion
 1013  
      * @param customActions The list of custom actions this digester needs
 1014  
      *                      to be able to process
 1015  
      */
 1016  
     private static void addCustomActionRules(final String xp,
 1017  
             final ExtendedBaseRules scxmlRules, final List customActions) {
 1018  270
         if (customActions == null || customActions.size() == 0) {
 1019  250
             return;
 1020  
         }
 1021  45
         for (int i = 0; i < customActions.size(); i++) {
 1022  25
             Object item = customActions.get(i);
 1023  25
             if (item == null || !(item instanceof CustomAction)) {
 1024  0
                 org.apache.commons.logging.Log log = LogFactory.
 1025  
                     getLog(SCXMLDigester.class);
 1026  0
                 log.warn(ERR_CUSTOM_ACTION_TYPE);
 1027  
             } else {
 1028  25
                 CustomAction ca = (CustomAction) item;
 1029  25
                 scxmlRules.setNamespaceURI(ca.getNamespaceURI());
 1030  25
                 String xpfLocalName = STR_SLASH + ca.getLocalName();
 1031  25
                 Class klass = ca.getActionClass();
 1032  25
                 if (SCXMLHelper.implementationOf(klass,
 1033  
                         ExternalContent.class)) {
 1034  0
                     addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules,
 1035  
                         klass, true);
 1036  
                 } else {
 1037  25
                     addCustomActionRulesTuple(xp + xpfLocalName, scxmlRules,
 1038  
                         klass, false);
 1039  
                 }
 1040  
             }
 1041  
         }
 1042  20
         scxmlRules.setNamespaceURI(NAMESPACE_SCXML);
 1043  20
     }
 1044  
 
 1045  
     /**
 1046  
      * Add Digester rules that are specific to the &lt;send&gt; action
 1047  
      * element.
 1048  
      *
 1049  
      * @param xp The Digester style XPath expression of &lt;send&gt; element
 1050  
      * @param scxmlRules The rule set to be used for digestion
 1051  
      */
 1052  
     private static void addSendRulesTuple(final String xp,
 1053  
             final ExtendedBaseRules scxmlRules) {
 1054  1458
         addActionRulesTuple(xp, scxmlRules, Send.class);
 1055  
         try {
 1056  1458
             scxmlRules.add(xp, new ParseExternalContentRule());
 1057  0
         } catch (ParserConfigurationException pce) {
 1058  0
             org.apache.commons.logging.Log log = LogFactory.
 1059  
                 getLog(SCXMLDigester.class);
 1060  0
             log.error(ERR_PARSER_CFG_SEND, pce);
 1061  1458
         }
 1062  1458
     }
 1063  
 
 1064  
     /**
 1065  
      * Add Digester rules for a simple custom action (no body content).
 1066  
      *
 1067  
      * @param xp The path to the custom action element
 1068  
      * @param scxmlRules The rule set to be used for digestion
 1069  
      * @param klass The <code>Action</code> class implementing the custom
 1070  
      *              action.
 1071  
      * @param bodyContent Whether the custom rule has body content
 1072  
      *              that should be parsed using
 1073  
      *              <code>NodeCreateRule</code>
 1074  
      */
 1075  
     private static void addCustomActionRulesTuple(final String xp,
 1076  
             final ExtendedBaseRules scxmlRules, final Class klass,
 1077  
             final boolean bodyContent) {
 1078  25
         addActionRulesTuple(xp, scxmlRules, klass);
 1079  25
         if (bodyContent) {
 1080  
             try {
 1081  0
                 scxmlRules.add(xp, new ParseExternalContentRule());
 1082  0
             } catch (ParserConfigurationException pce) {
 1083  0
                 org.apache.commons.logging.Log log = LogFactory.
 1084  
                     getLog(SCXMLDigester.class);
 1085  0
                 log.error(ERR_PARSER_CFG_CUSTOM, pce);
 1086  0
             }
 1087  
         }
 1088  25
     }
 1089  
 
 1090  
     /**
 1091  
      * Add Digester rules for all &lt;if&gt; elements.
 1092  
      *
 1093  
      * @param xp The Digester style XPath expression of the parent
 1094  
      *           XML element
 1095  
      * @param scxmlRules The rule set to be used for digestion
 1096  
      * @param pr The {@link PathResolver} for this document
 1097  
      * @param customActions The list of custom actions this digester needs
 1098  
      *                      to be able to process
 1099  
      */
 1100  
     private static void addIfRules(final String xp,
 1101  
             final ExtendedBaseRules scxmlRules, final PathResolver pr,
 1102  
             final List customActions) {
 1103  54
         addActionRulesTuple(xp, scxmlRules, If.class);
 1104  54
         addActionRules(xp, scxmlRules, pr, customActions);
 1105  54
         addActionRulesTuple(xp + XPF_EIF, scxmlRules, ElseIf.class);
 1106  54
         addActionRulesTuple(xp + XPF_ELS, scxmlRules, Else.class);
 1107  54
     }
 1108  
 
 1109  
     /**
 1110  
      * Add Digester rules that are common across all actions elements.
 1111  
      *
 1112  
      * @param xp The Digester style XPath expression of the parent
 1113  
      *           XML element
 1114  
      * @param scxmlRules The rule set to be used for digestion
 1115  
      * @param klass The class in the Java object model to be instantiated
 1116  
      *              in the ObjectCreateRule for this action
 1117  
      */
 1118  
     private static void addActionRulesTuple(final String xp,
 1119  
             final ExtendedBaseRules scxmlRules, final Class klass) {
 1120  8935
         addSimpleRulesTuple(xp, scxmlRules, klass, null, null, "addAction");
 1121  8935
         scxmlRules.add(xp, new SetExecutableParentRule());
 1122  8935
         scxmlRules.add(xp, new SetCurrentNamespacesRule());
 1123  8935
     }
 1124  
 
 1125  
     /**
 1126  
      * Add the run of the mill Digester rules for any element.
 1127  
      *
 1128  
      * @param xp The Digester style XPath expression of the parent
 1129  
      *           XML element
 1130  
      * @param scxmlRules The rule set to be used for digestion
 1131  
      * @param klass The class in the Java object model to be instantiated
 1132  
      *              in the ObjectCreateRule for this action
 1133  
      * @param args The attributes to be mapped into the object model
 1134  
      * @param props The properties that args get mapped to
 1135  
      * @param addMethod The method that the SetNextRule should call
 1136  
      */
 1137  
     private static void addSimpleRulesTuple(final String xp,
 1138  
             final ExtendedBaseRules scxmlRules, final Class klass,
 1139  
             final String[] args, final String[] props,
 1140  
             final String addMethod) {
 1141  8989
         scxmlRules.add(xp, new ObjectCreateRule(klass));
 1142  8989
         if (args == null) {
 1143  8989
             scxmlRules.add(xp, new SetPropertiesRule());
 1144  
         } else {
 1145  0
             scxmlRules.add(xp, new SetPropertiesRule(args, props));
 1146  
         }
 1147  8989
         scxmlRules.add(xp, new SetNextRule(addMethod));
 1148  8989
     }
 1149  
 
 1150  
     /**
 1151  
      * Discourage instantiation since this is a utility class.
 1152  
      */
 1153  
     private SCXMLDigester() {
 1154  0
         super();
 1155  0
     }
 1156  
 
 1157  
     /**
 1158  
      * Custom digestion rule for establishing necessary associations of this
 1159  
      * TransitionTarget with the root SCXML object.
 1160  
      * These include: <br>
 1161  
      * 1) Updation of the SCXML object's global targets Map <br>
 1162  
      * 2) Obtaining a handle to the SCXML object's NotificationRegistry <br>
 1163  
      *
 1164  
      * @deprecated Will be removed in version 1.0
 1165  
      */
 1166  
     public static class UpdateModelRule extends Rule {
 1167  
 
 1168  
         /**
 1169  
          * The root SCXML object.
 1170  
          */
 1171  
         private SCXML scxml;
 1172  
 
 1173  
         /**
 1174  
          * Constructor.
 1175  
          * @param scxml The root SCXML object
 1176  
          */
 1177  
         public UpdateModelRule(final SCXML scxml) {
 1178  702
             super();
 1179  702
             this.scxml = scxml;
 1180  702
         }
 1181  
 
 1182  
         /**
 1183  
          * @see Rule#end(String, String)
 1184  
          */
 1185  
         public final void end(final String namespace, final String name) {
 1186  294
             if (scxml == null) {
 1187  117
                 scxml = (SCXML) getDigester()
 1188  
                         .peek(getDigester().getCount() - 1);
 1189  
             }
 1190  294
             TransitionTarget tt = (TransitionTarget) getDigester().peek();
 1191  294
             scxml.addTarget(tt);
 1192  294
         }
 1193  
     }
 1194  
 
 1195  
     /**
 1196  
      * Custom digestion rule for setting Executable parent of Action elements.
 1197  
      *
 1198  
      * @deprecated Will be removed in version 1.0
 1199  
      */
 1200  
     public static class SetExecutableParentRule extends Rule {
 1201  
 
 1202  
         /**
 1203  
          * Constructor.
 1204  
          */
 1205  
         public SetExecutableParentRule() {
 1206  8935
             super();
 1207  8935
         }
 1208  
 
 1209  
         /**
 1210  
          * @see Rule#end(String, String)
 1211  
          */
 1212  
         public final void end(final String namespace, final String name) {
 1213  181
             Action child = (Action) getDigester().peek();
 1214  215
             for (int i = 1; i < getDigester().getCount() - 1; i++) {
 1215  215
                 Object ancestor = getDigester().peek(i);
 1216  215
                 if (ancestor instanceof Executable) {
 1217  181
                     child.setParent((Executable) ancestor);
 1218  181
                     return;
 1219  
                 }
 1220  
             }
 1221  0
         }
 1222  
     }
 1223  
 
 1224  
     /**
 1225  
      * Custom digestion rule for parsing bodies of
 1226  
      * <code>ExternalContent</code> elements.
 1227  
      *
 1228  
      * @see ExternalContent
 1229  
      *
 1230  
      * @deprecated Will be removed in version 1.0
 1231  
      */
 1232  
     public static class ParseExternalContentRule extends NodeCreateRule {
 1233  
         /**
 1234  
          * Constructor.
 1235  
          * @throws ParserConfigurationException A JAXP configuration error
 1236  
          */
 1237  
         public ParseExternalContentRule()
 1238  
         throws ParserConfigurationException {
 1239  1458
             super();
 1240  1458
         }
 1241  
         /**
 1242  
          * @see Rule#end(String, String)
 1243  
          */
 1244  
         public final void end(final String namespace, final String name) {
 1245  14
             Element bodyElement = (Element) getDigester().pop();
 1246  14
             NodeList childNodes = bodyElement.getChildNodes();
 1247  14
             List externalNodes = ((ExternalContent) getDigester().
 1248  
                 peek()).getExternalNodes();
 1249  20
             for (int i = 0; i < childNodes.getLength(); i++) {
 1250  6
                 externalNodes.add(childNodes.item(i));
 1251  
             }
 1252  14
         }
 1253  
     }
 1254  
 
 1255  
     /**
 1256  
      * Custom digestion rule for parsing bodies of &lt;data&gt; elements.
 1257  
      *
 1258  
      * @deprecated Will be removed in version 1.0
 1259  
      */
 1260  
     public static class ParseDataRule extends NodeCreateRule {
 1261  
 
 1262  
         /**
 1263  
          * The PathResolver used to resolve the src attribute to the
 1264  
          * SCXML document it points to.
 1265  
          * @see PathResolver
 1266  
          */
 1267  
         private PathResolver pr;
 1268  
 
 1269  
         /**
 1270  
          * The "src" attribute, retained to check if body content is legal.
 1271  
          */
 1272  
         private String src;
 1273  
 
 1274  
         /**
 1275  
          * The "expr" attribute, retained to check if body content is legal.
 1276  
          */
 1277  
         private String expr;
 1278  
 
 1279  
         /**
 1280  
          * The XML tree for this data, parse as a Node, obtained from
 1281  
          * either the "src" or the "expr" attributes.
 1282  
          */
 1283  
         private Node attrNode;
 1284  
 
 1285  
         /**
 1286  
          * Constructor.
 1287  
          *
 1288  
          * @param pr The <code>PathResolver</code>
 1289  
          * @throws ParserConfigurationException A JAXP configuration error
 1290  
          */
 1291  
         public ParseDataRule(final PathResolver pr)
 1292  
         throws ParserConfigurationException {
 1293  270
             super();
 1294  270
             this.pr = pr;
 1295  270
         }
 1296  
 
 1297  
         /**
 1298  
          * @see Rule#begin(String, String, Attributes)
 1299  
          */
 1300  
         public final void begin(final String namespace, final String name,
 1301  
                 final Attributes attributes) throws Exception {
 1302  64
             super.begin(namespace, name, attributes);
 1303  64
             src = attributes.getValue("src");
 1304  64
             expr = attributes.getValue("expr");
 1305  64
             if (!SCXMLHelper.isStringEmpty(src)) {
 1306  0
                 String path = null;
 1307  0
                 if (pr == null) {
 1308  0
                     path = src;
 1309  
                 } else {
 1310  0
                     path = pr.resolvePath(src);
 1311  
                 }
 1312  
                 try {
 1313  0
                     DocumentBuilderFactory dbFactory = DocumentBuilderFactory.
 1314  
                         newInstance();
 1315  0
                     DocumentBuilder db = dbFactory.newDocumentBuilder();
 1316  0
                     attrNode = db.parse(path);
 1317  0
                 } catch (Throwable t) { // you read that correctly
 1318  0
                     org.apache.commons.logging.Log log = LogFactory.
 1319  
                         getLog(SCXMLDigester.class);
 1320  0
                     log.error(t.getMessage(), t);
 1321  0
                 }
 1322  0
                 return;
 1323  
             }
 1324  64
         }
 1325  
 
 1326  
         /**
 1327  
          * @see Rule#end(String, String)
 1328  
          */
 1329  
         public final void end(final String namespace, final String name) {
 1330  64
             Node bodyNode = (Node) getDigester().pop();
 1331  64
             Data data = ((Data) getDigester().peek());
 1332  
             // Prefer "src" over "expr", "expr" over child nodes
 1333  
             // "expr" can only be evaluated at execution time
 1334  64
             if (!SCXMLHelper.isStringEmpty(src)) {
 1335  0
                 data.setNode(attrNode);
 1336  64
             } else  if (SCXMLHelper.isStringEmpty(expr)) {
 1337  
                 // both "src" and "expr" are empty
 1338  60
                 data.setNode(bodyNode);
 1339  
             }
 1340  64
         }
 1341  
     }
 1342  
 
 1343  
     /**
 1344  
      * Custom digestion rule for external sources, that is, the src attribute of
 1345  
      * the &lt;state&gt; element.
 1346  
      *
 1347  
      * @deprecated Will be removed in version 1.0
 1348  
      */
 1349  
     public static class DigestSrcAttributeRule extends Rule {
 1350  
 
 1351  
         /**
 1352  
          * The PathResolver used to resolve the src attribute to the
 1353  
          * SCXML document it points to.
 1354  
          * @see PathResolver
 1355  
          */
 1356  
         private PathResolver pr;
 1357  
 
 1358  
         /**
 1359  
          * The root document.
 1360  
          */
 1361  
         private SCXML root;
 1362  
 
 1363  
         /**
 1364  
          * The list of custom actions the parent document is capable of
 1365  
          * processing (and hence, the child should be, by transitivity).
 1366  
          * @see CustomAction
 1367  
          */
 1368  
         private List customActions;
 1369  
 
 1370  
         /**
 1371  
          * Constructor.
 1372  
          * @param pr The PathResolver
 1373  
          * @param customActions The list of custom actions this digester needs
 1374  
          *                      to be able to process
 1375  
          *
 1376  
          * @see PathResolver
 1377  
          * @see CustomAction
 1378  
          *
 1379  
          * TODO: Remove in v1.0
 1380  
          */
 1381  
         public DigestSrcAttributeRule(final List customActions,
 1382  
                 final PathResolver pr) {
 1383  0
             super();
 1384  0
             this.customActions = customActions;
 1385  0
             this.pr = pr;
 1386  0
         }
 1387  
 
 1388  
         /**
 1389  
          * Constructor.
 1390  
          * @param root The root document, if this one is src'ed in
 1391  
          * @param pr The PathResolver
 1392  
          * @param customActions The list of custom actions this digester needs
 1393  
          *                      to be able to process
 1394  
          *
 1395  
          * @see PathResolver
 1396  
          * @see CustomAction
 1397  
          */
 1398  
         public DigestSrcAttributeRule(final SCXML root,
 1399  
                 final List customActions, final PathResolver pr) {
 1400  648
             super();
 1401  648
             this.root = root;
 1402  648
             this.customActions = customActions;
 1403  648
             this.pr = pr;
 1404  648
         }
 1405  
 
 1406  
         /**
 1407  
          * @see Rule#begin(String, String, Attributes)
 1408  
          */
 1409  
         public final void begin(final String namespace, final String name,
 1410  
                 final Attributes attributes) {
 1411  289
             String src = attributes.getValue("src");
 1412  289
             if (SCXMLHelper.isStringEmpty(src)) {
 1413  286
                 return;
 1414  
             }
 1415  3
             Digester digester = getDigester();
 1416  3
             SCXML scxml = (SCXML) digester.peek(digester.getCount() - 1);
 1417  
             // 1) Digest the external SCXML file
 1418  3
             SCXML externalSCXML = null;
 1419  
             String path;
 1420  
             Digester externalSrcDigester;
 1421  3
             if (pr == null) {
 1422  0
                 path = src;
 1423  0
                 if (root != null) {
 1424  0
                     externalSrcDigester = newInstance(root, null,
 1425  
                         customActions);
 1426  
                 } else {
 1427  0
                     externalSrcDigester = newInstance(scxml, null,
 1428  
                         customActions);
 1429  
                 }
 1430  
             } else {
 1431  3
                 path = pr.resolvePath(src);
 1432  3
                 if (root != null) {
 1433  1
                     externalSrcDigester = newInstance(root,
 1434  
                         pr.getResolver(src), customActions);
 1435  
                 } else {
 1436  2
                     externalSrcDigester = newInstance(scxml,
 1437  
                         pr.getResolver(src), customActions);
 1438  
                 }
 1439  
             }
 1440  
 
 1441  
             try {
 1442  3
                 externalSCXML = (SCXML) externalSrcDigester.parse(path);
 1443  0
             } catch (Exception e) {
 1444  0
                 org.apache.commons.logging.Log log = LogFactory.
 1445  
                     getLog(SCXMLDigester.class);
 1446  0
                 log.error(e.getMessage(), e);
 1447  3
             }
 1448  
             // 2) Adopt the children and datamodel
 1449  3
             if (externalSCXML == null) {
 1450  0
                 return;
 1451  
             }
 1452  3
             State s = (State) digester.peek();
 1453  3
             Transition t = new Transition();
 1454  3
             t.setNext(externalSCXML.getInitialstate());
 1455  3
             Initial ini = new Initial();
 1456  3
             ini.setTransition(t);
 1457  3
             s.setInitial(ini);
 1458  3
             Map children = externalSCXML.getStates();
 1459  3
             Object[] ids = children.keySet().toArray();
 1460  8
             for (int i = 0; i < ids.length; i++) {
 1461  5
                 s.addChild((State) children.get(ids[i]));
 1462  
             }
 1463  3
             s.setDatamodel(externalSCXML.getDatamodel());
 1464  3
         }
 1465  
     }
 1466  
 
 1467  
     /**
 1468  
      * Custom digestion rule for setting PathResolver for runtime retrieval.
 1469  
      *
 1470  
      * @deprecated Will be removed in version 1.0
 1471  
      */
 1472  
     public static class SetPathResolverRule extends Rule {
 1473  
 
 1474  
         /**
 1475  
          * The PathResolver to set.
 1476  
          * @see PathResolver
 1477  
          */
 1478  
         private PathResolver pr;
 1479  
 
 1480  
         /**
 1481  
          * Constructor.
 1482  
          * @param pr The PathResolver
 1483  
          *
 1484  
          * @see PathResolver
 1485  
          */
 1486  
         public SetPathResolverRule(final PathResolver pr) {
 1487  1674
             super();
 1488  1674
             this.pr = pr;
 1489  1674
         }
 1490  
 
 1491  
         /**
 1492  
          * @see Rule#begin(String, String, Attributes)
 1493  
          */
 1494  
         public final void begin(final String namespace, final String name,
 1495  
                 final Attributes attributes) {
 1496  67
             PathResolverHolder prHolder = (PathResolverHolder) getDigester().
 1497  
                 peek();
 1498  67
             prHolder.setPathResolver(pr);
 1499  67
         }
 1500  
     }
 1501  
 
 1502  
     /**
 1503  
      * Custom digestion rule for setting state parent of finalize.
 1504  
      *
 1505  
      * @deprecated Will be removed in version 1.0
 1506  
      */
 1507  216
     public static class UpdateFinalizeRule extends Rule {
 1508  
 
 1509  
         /**
 1510  
          * @see Rule#begin(String, String, Attributes)
 1511  
          */
 1512  
         public final void begin(final String namespace, final String name,
 1513  
                 final Attributes attributes) {
 1514  1
             Finalize finalize = (Finalize) getDigester().peek();
 1515  
             // state/invoke/finalize --> peek(2)
 1516  1
             TransitionTarget tt = (TransitionTarget) getDigester().peek(2);
 1517  1
             finalize.setParent(tt);
 1518  1
         }
 1519  
     }
 1520  
 
 1521  
 
 1522  
     /**
 1523  
      * Custom digestion rule for attaching a snapshot of current namespaces
 1524  
      * to SCXML actions for deferred XPath evaluation.
 1525  
      *
 1526  
      */
 1527  20570
     private static class SetCurrentNamespacesRule extends Rule {
 1528  
 
 1529  
         /**
 1530  
          * @see Rule#begin(String, String, Attributes)
 1531  
          */
 1532  
         public final void begin(final String namespace, final String name,
 1533  
                 final Attributes attributes) {
 1534  488
             NamespacePrefixesHolder nsHolder =
 1535  
                 (NamespacePrefixesHolder) getDigester().peek();
 1536  488
             nsHolder.setNamespaces(getDigester().getCurrentNamespaces());
 1537  488
         }
 1538  
     }
 1539  
 
 1540  
 }
 1541