2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

Coverage Report - org.apache.shale.dialog.scxml.config.ConfigurationParser
 
Classes in this File Line Coverage Branch Coverage Complexity
ConfigurationParser
0%
0/77
0%
0/10
1.909
ConfigurationParser$AddDialogMetadataRule
0%
0/6
N/A
1.909
 
 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  
 
 18  
 package org.apache.shale.dialog.scxml.config;
 19  
 
 20  
 import java.io.IOException;
 21  
 import java.net.URL;
 22  
 import java.util.ArrayList;
 23  
 import java.util.HashMap;
 24  
 import java.util.Iterator;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 
 28  
 import org.apache.commons.digester.Digester;
 29  
 import org.apache.commons.digester.Rule;
 30  
 import org.apache.commons.scxml.env.SimpleErrorHandler;
 31  
 import org.apache.commons.scxml.io.SCXMLDigester;
 32  
 import org.apache.commons.scxml.model.CustomAction;
 33  
 import org.apache.commons.scxml.model.ModelException;
 34  
 import org.apache.commons.scxml.model.SCXML;
 35  
 import org.apache.shale.dialog.scxml.Globals;
 36  
 import org.apache.shale.dialog.scxml.action.RedirectAction;
 37  
 import org.apache.shale.dialog.scxml.action.ViewAction;
 38  
 import org.apache.shale.dialog.scxml.config.DialogMetadata.SCXMLAction;
 39  
 import org.xml.sax.SAXException;
 40  
 
 41  
 /**
 42  
  * <p>Configuration utility for parsing SCXML documents as resources for
 43  
  * defining dialogs.  This class has no dependencies on web tier APIs,
 44  
  * only on the Commons SCXML state machine engine library, and
 45  
  * on the parsing technology (Commons Digester) being used.</p>
 46  
  *
 47  
  * <p>The configuration for each Shale dialog exists as a standalone
 48  
  * SCXML document, with additional dialog "metadata" file(s)
 49  
  * that serve as the entry point for the Shale Dialog Manager.</p>
 50  
  *
 51  
  * <p>These dialog-config.xml file(s) look like this:
 52  
  * <pre>
 53  
  * &lt;dialogs&gt;
 54  
  *     &lt;dialog name="Foo" scxmlconfig="foo.scxml" /&gt;
 55  
  *     &lt;dialog name="Bar" scxmlconfig="bar.scxml"
 56  
  *             dataclassname="org.apache.shale.examples.Bar" /&gt;
 57  
  *     &lt;dialog name="Baz" scxmlconfig="baz.scxml" /&gt;
 58  
  *     &lt;!-- etc. --&gt;
 59  
  * &lt;/dialogs&gt;
 60  
  * </pre>
 61  
  * </p>
 62  
  *
 63  
  * <p>To use this utility, instantiate a new instance and set the
 64  
  * <code>dialogs</code>, <code>resource</code>, and <code>validating</code>
 65  
  * properties.  Then, call the <code>parse()</code> method.  You can parse
 66  
  * more than one resource by resetting the <code>resource</code>
 67  
  * property and calling <code>parse()</code> again.</p>
 68  
  *
 69  
  * @since 1.0.4
 70  
  */
 71  
 
 72  0
 public final class ConfigurationParser {
 73  
 
 74  
 
 75  
     // -------------------------------------------------------------- Properties
 76  
 
 77  
     /**
 78  
      * <p>Registration information for the DTD we will use to validate.</p>
 79  
      */
 80  0
     private static final String[] REGISTRATIONS =
 81  
     { "-//Apache Software Foundation//DTD Shale SCXML Dialog Configuration 1.0//EN",
 82  
       "/org/apache/shale/dialog/scxml/dialog-scxml-config_1_0.dtd" };
 83  
 
 84  
 
 85  
     /**
 86  
      * <p><code>Map</code> of <code>Dialog</code> instances resulting
 87  
      * from parsing, keyed by dialog name.</p>
 88  
      */
 89  0
     private Map dialogs = null;
 90  
 
 91  
 
 92  
     /**
 93  
      * <p>Return the <code>Map</code> of <code>Dialog</code> instances
 94  
      * into which parsed information will be stored, keyed by dialog
 95  
      * name.</p>
 96  
      *
 97  
      * @return Map of SCXML instances, keyed by logical dialog name
 98  
      */
 99  
     public Map getDialogs() {
 100  0
         return this.dialogs;
 101  
     }
 102  
 
 103  
 
 104  
     /**
 105  
      * <p>Set the <code>Map</code> of <code>Dialog</code> instances
 106  
      * into which parsed information will be stored, keyed by dialog
 107  
      * name.</p>
 108  
      *
 109  
      * @param dialogs The new map
 110  
      */
 111  
     public void setDialogs(Map dialogs) {
 112  0
         this.dialogs = dialogs;
 113  0
     }
 114  
 
 115  
 
 116  
     /**
 117  
      * <p>The URL of the configuration resource to be parsed.</p>
 118  
      */
 119  0
     private URL resource = null;
 120  
 
 121  
 
 122  
     /**
 123  
      * <p>Return the URL of the configuration resource to be parsed.</p>
 124  
      *
 125  
      * @return The resource URL
 126  
      */
 127  
     public URL getResource() {
 128  0
         return this.resource;
 129  
     }
 130  
 
 131  
 
 132  
     /**
 133  
      * <p>Set the URL of the configuration resource to be parsed.</p>
 134  
      *
 135  
      * @param resource The new resource URL
 136  
      */
 137  
     public void setResource(URL resource) {
 138  0
         this.resource = resource;
 139  0
     }
 140  
 
 141  
 
 142  
     /**
 143  
      * <p>Flag indicating whether we should do a validating parse or not.</p>
 144  
      */
 145  0
     private boolean validating = true;
 146  
 
 147  
 
 148  
     /**
 149  
      * <p>Return a flag indicating whether we will be doing a validating parse
 150  
      * or not.  Default value is <code>false</code>.</p>
 151  
      *
 152  
      * @return Whether the parse is validating
 153  
      */
 154  
     public boolean isValidating() {
 155  0
         return this.validating;
 156  
     }
 157  
 
 158  
 
 159  
     /**
 160  
      * <p>Set a flag indicating whether we will be doing a validating parse
 161  
      * or not.</p>
 162  
      *
 163  
      * @param validating New flag value
 164  
      */
 165  
     public void setValidating(boolean validating) {
 166  0
         this.validating = validating;
 167  0
     }
 168  
 
 169  
 
 170  
     // ---------------------------------------------------------- Public Methods
 171  
 
 172  
 
 173  
     /**
 174  
      * <p>Parse the configuration resource identified by the <code>resource</code>
 175  
      * property, storing resulting information in the <code>Map</code> specified
 176  
      * by the <code>dialogs</code> property.</p>
 177  
      *
 178  
      * @exception IOException if an input/output error occurs
 179  
      * @exception SAXException if an XML parsing error occurs
 180  
      */
 181  
     public void parse() throws IOException, SAXException {
 182  
 
 183  0
         Map metadata = new HashMap();
 184  0
         Digester digester = digester();
 185  0
         digester.clear();
 186  0
         digester.push(metadata);
 187  0
         digester.parse(getResource());
 188  
 
 189  0
         parseDialogs(metadata);
 190  0
     }
 191  
 
 192  
 
 193  
     // --------------------------------------------------------- Private Methods
 194  
 
 195  
 
 196  
     /**
 197  
      * <p>Return a fully configured <code>Digester</code> instance.</p>
 198  
      *
 199  
      * @return The configuration parser Digester instance
 200  
      */
 201  
     private Digester digester() {
 202  
 
 203  0
         Digester digester = new Digester();
 204  
 
 205  
         // Configure global characteristics
 206  0
         digester.setNamespaceAware(false);
 207  0
         digester.setUseContextClassLoader(true);
 208  0
         digester.setValidating(isValidating());
 209  
 
 210  
         // Register local copy of our DTDs
 211  0
         for (int i = 0; i < REGISTRATIONS.length; i += 2) {
 212  0
             URL url = this.getClass().getResource(REGISTRATIONS[i + 1]);
 213  0
             digester.register(REGISTRATIONS[i], url);
 214  
         }
 215  
 
 216  
         // Configure processing rules
 217  
 
 218  
         // dialogs/dialog
 219  0
         digester.addObjectCreate("dialogs/dialog", DialogMetadata.class);
 220  0
         digester.addSetProperties("dialogs/dialog");
 221  0
         digester.addRule("dialogs/dialog", new AddDialogMetadataRule());
 222  
 
 223  0
         digester.addObjectCreate("dialogs/dialog/scxmlaction", SCXMLAction.class);
 224  0
         digester.addSetProperties("dialogs/dialog/scxmlaction");
 225  0
         digester.addSetNext("dialogs/dialog/scxmlaction", "addDialogAction");
 226  
 
 227  0
         return digester;
 228  
 
 229  
     }
 230  
 
 231  
 
 232  
     /**
 233  
      * <p>Parse the SCXML documents in the dialog metadata, storing resulting
 234  
      * information as an entry in the <code>Map</code> specified by the
 235  
      * <code>dialogs</code> property.</p>
 236  
      *
 237  
      * @param metadata The metadata map
 238  
      * @throws IOException if an input/output error occurs
 239  
      * @throws SAXException if an XML parsing error occurs
 240  
      */
 241  
     private void parseDialogs(Map metadata) throws IOException, SAXException {
 242  
 
 243  0
         Iterator iterator = metadata.entrySet().iterator();
 244  
 
 245  
         // Create a list of the custom Commons SCXML actions defined by the
 246  
         // Shale dialog Commons SCXML implementation
 247  0
         List shaleDialogActions = new ArrayList();
 248  
 
 249  
         // <shale:redirect>
 250  0
         CustomAction redirectAction =
 251  
             new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
 252  
                 "redirect", RedirectAction.class);
 253  0
         shaleDialogActions.add(redirectAction);
 254  
 
 255  
         // <shale:view>
 256  0
         CustomAction viewAction =
 257  
             new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
 258  
                 "view", ViewAction.class);
 259  0
         shaleDialogActions.add(viewAction);
 260  
 
 261  
         // Class loader for app developer defined custom Commons SCXML actions
 262  0
         ClassLoader loader = Thread.currentThread().getContextClassLoader();
 263  0
         if (loader == null) {
 264  0
             loader = ConfigurationParser.class.getClassLoader();
 265  
         }
 266  
 
 267  0
         while (iterator.hasNext()) {
 268  
 
 269  0
                 Map.Entry entry = (Map.Entry) iterator.next();
 270  0
                 String name = (String) entry.getKey();
 271  0
                 DialogMetadata dMetadata = (DialogMetadata) entry.getValue();
 272  0
                 String scxmlconfig = dMetadata.getScxmlconfig();
 273  
 
 274  
                 // The custom actions available to this dialog is the
 275  
                 // summation of the ones defined by this Shale dialog module
 276  
                 // and those defined by the app developer using the dialog
 277  
                 // configuration file for this dialog
 278  0
                 List customDialogActions = new ArrayList();
 279  0
                 customDialogActions.addAll(shaleDialogActions);
 280  
 
 281  0
                 List devActions = dMetadata.getDialogActions();
 282  0
                 for (int i = 0; i < devActions.size(); i++) {
 283  0
                     SCXMLAction scxmlAction = (SCXMLAction) devActions.get(i);
 284  0
                     String actionname = scxmlAction.getName();
 285  0
                     String uri = scxmlAction.getUri();
 286  0
                     String actionFQCN = scxmlAction.getActionclassname();
 287  0
                     if (actionname == null || uri == null || actionFQCN == null) {
 288  
                         // shouldn't happen if dialog-config is validated
 289  0
                         throw new IllegalArgumentException("A custom Commons"
 290  
                             + " SCXML action (<scxmlaction> element) in the"
 291  
                             + " dialog configuration is missing the 'name',"
 292  
                             + " 'uri' or 'actionclassname'");
 293  
                     }
 294  0
                     Class customActionClass = null;
 295  
                     try {
 296  0
                         customActionClass = loader.loadClass(actionFQCN);
 297  0
                     } catch (Exception e) {
 298  0
                         throw new IllegalArgumentException("Cannot load "
 299  
                             + "custom Commons SCXML action class '"
 300  
                             + actionFQCN + "' for action with name '"
 301  
                             + actionname + "'");
 302  0
                     }
 303  0
                     CustomAction customAction = new CustomAction(uri,
 304  
                         actionname, customActionClass);
 305  0
                     customDialogActions.add(customAction);
 306  
                 }
 307  
 
 308  0
                 URL resource = new URL(getResource(), scxmlconfig);
 309  
 
 310  0
                 SCXML dialog = null;
 311  
                 try {
 312  
                     // Parse document, with rules for custom actions in place
 313  0
                     dialog = SCXMLDigester.digest(resource,
 314  
                         new SimpleErrorHandler(), customDialogActions);
 315  0
                 } catch (ModelException me) {
 316  0
                     throw new SAXException(me.getMessage(), me);
 317  0
                 }
 318  
 
 319  0
                 dMetadata.setStateMachine(dialog);
 320  0
                 dialogs.put(name, dMetadata);
 321  
 
 322  0
         }
 323  
 
 324  0
     }
 325  
 
 326  
 
 327  
     // -------------------------------------------- Private Rule Implementations
 328  
 
 329  
 
 330  
     /**
 331  
      * <p>Custom <code>Digester</code> rule to add a dialog.</p>
 332  
      */
 333  0
     static class AddDialogMetadataRule extends Rule {
 334  
 
 335  
         /**
 336  
          * Constructor.
 337  
          */
 338  
         public AddDialogMetadataRule() {
 339  0
             super();
 340  0
         }
 341  
 
 342  
         /**
 343  
          * {@inheritDoc}
 344  
          *
 345  
          * @see Rule#end(String,String)
 346  
          */
 347  
         public void end(String namespace, String name) throws Exception {
 348  
 
 349  0
             DialogMetadata dialog = (DialogMetadata) getDigester().peek();
 350  0
             Map map = (Map) getDigester().peek(1);
 351  0
             map.put(dialog.getName(), dialog);
 352  
 
 353  0
         }
 354  
 
 355  
     }
 356  
 
 357  
 }
 358