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

For more information, please explore the Attic.

View Javadoc

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  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      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      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         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         this.dialogs = dialogs;
113     }
114 
115 
116     /***
117      * <p>The URL of the configuration resource to be parsed.</p>
118      */
119     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         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         this.resource = resource;
139     }
140 
141 
142     /***
143      * <p>Flag indicating whether we should do a validating parse or not.</p>
144      */
145     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         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         this.validating = validating;
167     }
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         Map metadata = new HashMap();
184         Digester digester = digester();
185         digester.clear();
186         digester.push(metadata);
187         digester.parse(getResource());
188 
189         parseDialogs(metadata);
190     }
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         Digester digester = new Digester();
204 
205         // Configure global characteristics
206         digester.setNamespaceAware(false);
207         digester.setUseContextClassLoader(true);
208         digester.setValidating(isValidating());
209 
210         // Register local copy of our DTDs
211         for (int i = 0; i < REGISTRATIONS.length; i += 2) {
212             URL url = this.getClass().getResource(REGISTRATIONS[i + 1]);
213             digester.register(REGISTRATIONS[i], url);
214         }
215 
216         // Configure processing rules
217 
218         // dialogs/dialog
219         digester.addObjectCreate("dialogs/dialog", DialogMetadata.class);
220         digester.addSetProperties("dialogs/dialog");
221         digester.addRule("dialogs/dialog", new AddDialogMetadataRule());
222 
223         digester.addObjectCreate("dialogs/dialog/scxmlaction", SCXMLAction.class);
224         digester.addSetProperties("dialogs/dialog/scxmlaction");
225         digester.addSetNext("dialogs/dialog/scxmlaction", "addDialogAction");
226 
227         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         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         List shaleDialogActions = new ArrayList();
248 
249         // <shale:redirect>
250         CustomAction redirectAction =
251             new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
252                 "redirect", RedirectAction.class);
253         shaleDialogActions.add(redirectAction);
254 
255         // <shale:view>
256         CustomAction viewAction =
257             new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
258                 "view", ViewAction.class);
259         shaleDialogActions.add(viewAction);
260 
261         // Class loader for app developer defined custom Commons SCXML actions
262         ClassLoader loader = Thread.currentThread().getContextClassLoader();
263         if (loader == null) {
264             loader = ConfigurationParser.class.getClassLoader();
265         }
266 
267         while (iterator.hasNext()) {
268 
269                 Map.Entry entry = (Map.Entry) iterator.next();
270                 String name = (String) entry.getKey();
271                 DialogMetadata dMetadata = (DialogMetadata) entry.getValue();
272                 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                 List customDialogActions = new ArrayList();
279                 customDialogActions.addAll(shaleDialogActions);
280 
281                 List devActions = dMetadata.getDialogActions();
282                 for (int i = 0; i < devActions.size(); i++) {
283                     SCXMLAction scxmlAction = (SCXMLAction) devActions.get(i);
284                     String actionname = scxmlAction.getName();
285                     String uri = scxmlAction.getUri();
286                     String actionFQCN = scxmlAction.getActionclassname();
287                     if (actionname == null || uri == null || actionFQCN == null) {
288                         // shouldn't happen if dialog-config is validated
289                         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                     Class customActionClass = null;
295                     try {
296                         customActionClass = loader.loadClass(actionFQCN);
297                     } catch (Exception e) {
298                         throw new IllegalArgumentException("Cannot load "
299                             + "custom Commons SCXML action class '"
300                             + actionFQCN + "' for action with name '"
301                             + actionname + "'");
302                     }
303                     CustomAction customAction = new CustomAction(uri,
304                         actionname, customActionClass);
305                     customDialogActions.add(customAction);
306                 }
307 
308                 URL resource = new URL(getResource(), scxmlconfig);
309 
310                 SCXML dialog = null;
311                 try {
312                     // Parse document, with rules for custom actions in place
313                     dialog = SCXMLDigester.digest(resource,
314                         new SimpleErrorHandler(), customDialogActions);
315                 } catch (ModelException me) {
316                     throw new SAXException(me.getMessage(), me);
317                 }
318 
319                 dMetadata.setStateMachine(dialog);
320                 dialogs.put(name, dMetadata);
321 
322         }
323 
324     }
325 
326 
327     // -------------------------------------------- Private Rule Implementations
328 
329 
330     /***
331      * <p>Custom <code>Digester</code> rule to add a dialog.</p>
332      */
333     static class AddDialogMetadataRule extends Rule {
334 
335         /***
336          * Constructor.
337          */
338         public AddDialogMetadataRule() {
339             super();
340         }
341 
342         /***
343          * {@inheritDoc}
344          *
345          * @see Rule#end(String,String)
346          */
347         public void end(String namespace, String name) throws Exception {
348 
349             DialogMetadata dialog = (DialogMetadata) getDigester().peek();
350             Map map = (Map) getDigester().peek(1);
351             map.put(dialog.getName(), dialog);
352 
353         }
354 
355     }
356 
357 }
358