2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 * <dialogs>
54 * <dialog name="Foo" scxmlconfig="foo.scxml" />
55 * <dialog name="Bar" scxmlconfig="bar.scxml"
56 * dataclassname="org.apache.shale.examples.Bar" />
57 * <dialog name="Baz" scxmlconfig="baz.scxml" />
58 * <!-- etc. -->
59 * </dialogs>
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
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
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
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
206 digester.setNamespaceAware(false);
207 digester.setUseContextClassLoader(true);
208 digester.setValidating(isValidating());
209
210
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
217
218
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
246
247 List shaleDialogActions = new ArrayList();
248
249
250 CustomAction redirectAction =
251 new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
252 "redirect", RedirectAction.class);
253 shaleDialogActions.add(redirectAction);
254
255
256 CustomAction viewAction =
257 new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
258 "view", ViewAction.class);
259 shaleDialogActions.add(viewAction);
260
261
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
275
276
277
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
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
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
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