org.apache.shale.dialog.scxml.config.ConfigurationParser Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shale.dialog.scxml.config;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.commons.scxml.env.SimpleErrorHandler;
import org.apache.commons.scxml.io.SCXMLDigester;
import org.apache.commons.scxml.model.CustomAction;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.model.SCXML;
import org.apache.shale.dialog.scxml.Globals;
import org.apache.shale.dialog.scxml.action.RedirectAction;
import org.apache.shale.dialog.scxml.action.ViewAction;
import org.apache.shale.dialog.scxml.config.DialogMetadata.SCXMLAction;
import org.xml.sax.SAXException;
/**
* Configuration utility for parsing SCXML documents as resources for
* defining dialogs. This class has no dependencies on web tier APIs,
* only on the Commons SCXML state machine engine library, and
* on the parsing technology (Commons Digester) being used.
*
* The configuration for each Shale dialog exists as a standalone
* SCXML document, with additional dialog "metadata" file(s)
* that serve as the entry point for the Shale Dialog Manager.
*
* These dialog-config.xml file(s) look like this:
*
* <dialogs>
* <dialog name="Foo" scxmlconfig="foo.scxml" />
* <dialog name="Bar" scxmlconfig="bar.scxml"
* dataclassname="org.apache.shale.examples.Bar" />
* <dialog name="Baz" scxmlconfig="baz.scxml" />
* <!-- etc. -->
* </dialogs>
*
*
*
* To use this utility, instantiate a new instance and set the
* dialogs
, resource
, and validating
* properties. Then, call the parse()
method. You can parse
* more than one resource by resetting the resource
* property and calling parse()
again.
*
* @since 1.0.4
*/
public final class ConfigurationParser {
// -------------------------------------------------------------- Properties
/**
* Registration information for the DTD we will use to validate.
*/
private static final String[] REGISTRATIONS =
{ "-//Apache Software Foundation//DTD Shale SCXML Dialog Configuration 1.0//EN",
"/org/apache/shale/dialog/scxml/dialog-scxml-config_1_0.dtd" };
/**
* Map
of Dialog
instances resulting
* from parsing, keyed by dialog name.
*/
private Map dialogs = null;
/**
* Return the Map
of Dialog
instances
* into which parsed information will be stored, keyed by dialog
* name.
*
* @return Map of SCXML instances, keyed by logical dialog name
*/
public Map getDialogs() {
return this.dialogs;
}
/**
* Set the Map
of Dialog
instances
* into which parsed information will be stored, keyed by dialog
* name.
*
* @param dialogs The new map
*/
public void setDialogs(Map dialogs) {
this.dialogs = dialogs;
}
/**
* The URL of the configuration resource to be parsed.
*/
private URL resource = null;
/**
* Return the URL of the configuration resource to be parsed.
*
* @return The resource URL
*/
public URL getResource() {
return this.resource;
}
/**
* Set the URL of the configuration resource to be parsed.
*
* @param resource The new resource URL
*/
public void setResource(URL resource) {
this.resource = resource;
}
/**
* Flag indicating whether we should do a validating parse or not.
*/
private boolean validating = true;
/**
* Return a flag indicating whether we will be doing a validating parse
* or not. Default value is false
.
*
* @return Whether the parse is validating
*/
public boolean isValidating() {
return this.validating;
}
/**
* Set a flag indicating whether we will be doing a validating parse
* or not.
*
* @param validating New flag value
*/
public void setValidating(boolean validating) {
this.validating = validating;
}
// ---------------------------------------------------------- Public Methods
/**
* Parse the configuration resource identified by the resource
* property, storing resulting information in the Map
specified
* by the dialogs
property.
*
* @exception IOException if an input/output error occurs
* @exception SAXException if an XML parsing error occurs
*/
public void parse() throws IOException, SAXException {
Map metadata = new HashMap();
Digester digester = digester();
digester.clear();
digester.push(metadata);
digester.parse(getResource());
parseDialogs(metadata);
}
// --------------------------------------------------------- Private Methods
/**
* Return a fully configured Digester
instance.
*
* @return The configuration parser Digester instance
*/
private Digester digester() {
Digester digester = new Digester();
// Configure global characteristics
digester.setNamespaceAware(false);
digester.setUseContextClassLoader(true);
digester.setValidating(isValidating());
// Register local copy of our DTDs
for (int i = 0; i < REGISTRATIONS.length; i += 2) {
URL url = this.getClass().getResource(REGISTRATIONS[i + 1]);
digester.register(REGISTRATIONS[i], url);
}
// Configure processing rules
// dialogs/dialog
digester.addObjectCreate("dialogs/dialog", DialogMetadata.class);
digester.addSetProperties("dialogs/dialog");
digester.addRule("dialogs/dialog", new AddDialogMetadataRule());
digester.addObjectCreate("dialogs/dialog/scxmlaction", SCXMLAction.class);
digester.addSetProperties("dialogs/dialog/scxmlaction");
digester.addSetNext("dialogs/dialog/scxmlaction", "addDialogAction");
return digester;
}
/**
* Parse the SCXML documents in the dialog metadata, storing resulting
* information as an entry in the Map
specified by the
* dialogs
property.
*
* @param metadata The metadata map
* @throws IOException if an input/output error occurs
* @throws SAXException if an XML parsing error occurs
*/
private void parseDialogs(Map metadata) throws IOException, SAXException {
Iterator iterator = metadata.entrySet().iterator();
// Create a list of the custom Commons SCXML actions defined by the
// Shale dialog Commons SCXML implementation
List shaleDialogActions = new ArrayList();
//
CustomAction redirectAction =
new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
"redirect", RedirectAction.class);
shaleDialogActions.add(redirectAction);
//
CustomAction viewAction =
new CustomAction(Globals.CUSTOM_SCXML_ACTIONS_URI,
"view", ViewAction.class);
shaleDialogActions.add(viewAction);
// Class loader for app developer defined custom Commons SCXML actions
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = ConfigurationParser.class.getClassLoader();
}
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
String name = (String) entry.getKey();
DialogMetadata dMetadata = (DialogMetadata) entry.getValue();
String scxmlconfig = dMetadata.getScxmlconfig();
// The custom actions available to this dialog is the
// summation of the ones defined by this Shale dialog module
// and those defined by the app developer using the dialog
// configuration file for this dialog
List customDialogActions = new ArrayList();
customDialogActions.addAll(shaleDialogActions);
List devActions = dMetadata.getDialogActions();
for (int i = 0; i < devActions.size(); i++) {
SCXMLAction scxmlAction = (SCXMLAction) devActions.get(i);
String actionname = scxmlAction.getName();
String uri = scxmlAction.getUri();
String actionFQCN = scxmlAction.getActionclassname();
if (actionname == null || uri == null || actionFQCN == null) {
// shouldn't happen if dialog-config is validated
throw new IllegalArgumentException("A custom Commons"
+ " SCXML action ( element) in the"
+ " dialog configuration is missing the 'name',"
+ " 'uri' or 'actionclassname'");
}
Class customActionClass = null;
try {
customActionClass = loader.loadClass(actionFQCN);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot load "
+ "custom Commons SCXML action class '"
+ actionFQCN + "' for action with name '"
+ actionname + "'");
}
CustomAction customAction = new CustomAction(uri,
actionname, customActionClass);
customDialogActions.add(customAction);
}
URL resource = new URL(getResource(), scxmlconfig);
SCXML dialog = null;
try {
// Parse document, with rules for custom actions in place
dialog = SCXMLDigester.digest(resource,
new SimpleErrorHandler(), customDialogActions);
} catch (ModelException me) {
throw new SAXException(me.getMessage(), me);
}
dMetadata.setStateMachine(dialog);
dialogs.put(name, dMetadata);
}
}
// -------------------------------------------- Private Rule Implementations
/**
* Custom Digester
rule to add a dialog.
*/
static class AddDialogMetadataRule extends Rule {
/**
* Constructor.
*/
public AddDialogMetadataRule() {
super();
}
/**
* {@inheritDoc}
*
* @see Rule#end(String,String)
*/
public void end(String namespace, String name) throws Exception {
DialogMetadata dialog = (DialogMetadata) getDigester().peek();
Map map = (Map) getDigester().peek(1);
map.put(dialog.getName(), dialog);
}
}
}