/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: XPDLUtil.java 2904 2009-01-28 22:21:36Z mlipp $
*
* $Log$
* Revision 1.7 2007/02/27 14:34:17 drmlipp
* Some refactoring to reduce cyclic dependencies.
*
* Revision 1.6 2006/11/20 09:52:37 drmlipp
* Fixed problems with using URL class.
*
* Revision 1.5 2006/11/19 11:16:35 mlipp
* Made ExternalReference an interface.
*
* Revision 1.4 2006/11/17 21:39:44 mlipp
* Adapted to Java type support.
*
* Revision 1.3 2006/11/17 16:16:12 drmlipp
* Started support for Java native types.
*
* Revision 1.2 2006/09/29 12:32:10 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.1.1.5 2004/08/18 15:17:39 drmlipp
* Update to 1.2
*
* Revision 1.22 2004/04/29 15:39:31 lipp
* Getting on with SAX based initialization.
*
* Revision 1.21 2004/03/25 14:41:47 lipp
* Added possibility to specify actual parameters as XML.
*
* Revision 1.20 2004/01/19 12:30:58 lipp
* SchemaType now supported properly.
*
* Revision 1.19 2003/10/23 08:50:42 lipp
* Fixed comment.
*
* Revision 1.18 2003/09/25 11:02:10 lipp
* Added support for remote resources in evaluation.
*
* Revision 1.17 2003/09/20 22:57:20 lipp
* Added variant for parseDuration.
*
* Revision 1.16 2003/09/20 19:26:06 lipp
* Modified complex time value specification handling.
*
* Revision 1.15 2003/09/05 12:38:12 lipp
* Added duration parsing for XPDL.
*
* Revision 1.14 2003/07/04 08:08:45 lipp
* Started variable performer.
*
* Revision 1.13 2003/06/27 08:51:44 lipp
* Fixed copyright/license information.
*
* Revision 1.12 2003/06/10 14:46:44 huaiyang
* Fix the error in generating sax events.
*
* Revision 1.11 2003/06/05 21:30:35 lipp
* Better handling of initial XML values.
*
* Revision 1.10 2003/06/03 16:38:56 lipp
* Updated to jdom b9.
*
* Revision 1.9 2003/05/15 07:25:54 lipp
* Javadoc fix.
*
* Revision 1.8 2003/04/25 20:05:32 lipp
* Retrieving participants from SAX now.
*
* Revision 1.7 2003/04/22 16:56:12 lipp
* Added missing pack().
*
* Revision 1.6 2003/04/22 16:36:28 lipp
* Handling schema types.
*
* Revision 1.5 2003/04/22 14:34:57 lipp
* Retrieving context signature from SAX.
*
* Revision 1.4 2003/04/01 11:16:08 lipp
* Handling for XML init data added.
*
* Revision 1.3 2003/03/28 14:42:35 lipp
* Moved some code to XPDLUtil.
*
* Revision 1.2 2003/03/28 12:45:24 lipp
* Moved XPDL related constants to XPDLUtil.
*
* Revision 1.1 2003/03/28 11:41:59 lipp
* More changes for data type support.
*
*/
package de.danet.an.workflow.util;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.net.URI;
import java.net.URISyntaxException;
import java.rmi.RemoteException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.output.SAXOutputter;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;
import de.danet.an.util.Duration;
import de.danet.an.util.Duration.ValueEvaluator;
import de.danet.an.util.XMLUtil;
import de.danet.an.util.sax.SAXContentBuffer;
import de.danet.an.util.sax.StackedHandler;
import de.danet.an.util.sax.XmlnsUrisPatcher;
import de.danet.an.workflow.api.ExternalReference;
import de.danet.an.workflow.api.Participant;
import de.danet.an.workflow.api.SAXEventBuffer;
/**
* This class provides some methods that help handling XPDL.
*
* @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
* @version $Revision: 2904 $
*/
public class XPDLUtil {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(XPDLUtil.class);
/** The XPDL version used. */
public static final String XPDL_VERSION
= "WFMC-TC-1025 Version 1.0 (Beta)";
/** The URI of the XPDL version used. */
public static final String XPDL_NS
= "http://www.wfmc.org/2002/XPDL1.0";
/** The URI of the supported XPDL extensions. */
public static final String XPDL_EXTN_NS
= "http://www.an.danet.de/2002/XPDL-Extensions1.0";
/** The URI of the supported XPDL extensions (v1.1). */
public static final String XPDL_EXTN_V1_1_NS
= "http://www.an.danet.de/2009/XPDL-Extensions1.1";
private static DateFormat dfRfc822plus = new SimpleDateFormat
("d MMM yy HH:mm:ss z", Locale.US);
private static DateFormat dfRfc822 = new SimpleDateFormat
("d MMM yy HH:mm z", Locale.US);
/**
* Check if the given namespace is our XPDL extension namespace.
* @param ns the namespace to check
* @return the result
*/
public static boolean isXpdlExtNs (String ns) {
return (ns.equals(XPDL_EXTN_NS) || ns.equals(XPDL_EXTN_V1_1_NS));
}
/**
* Extract the Java data type information from a
* <code>DataType</code> node. The type information is coded as follows:
* <ul>
* <li>
* if the type is a <code><BasicType></code>, the corresponding
* Java class is returned, with
* {@link de.danet.an.workflow.api.Participant <code>Participant</code>}
* used to denote "PERFORMER".
* </li>
* <li>
* if the type is <code><SchemaType></code>, but no schema is
* specified, <code>org.w3c.dom.Element.class</code> is returned.
* </li>
* <li>
* if the type is <code><SchemaType></code>, and the schema is
* specified, the schema is returned is returned
* (as <code>SAXEventBuffer</code>).
* </li>
* <li>
* if the type is <code><ExternalReference></code>,
* an <code>ExternalReference</code> is returned.
* </li>
* </ul>
* @param typeElem a <code><DataType></code> element.
* @return the type information as specified above.
* @throws IllegalArgumentException if the type is not recognized.
*/
public static Object extractDataType (Element typeElem)
throws IllegalArgumentException {
Namespace xpdlns = Namespace.getNamespace (XPDL_NS);
Element bt = typeElem.getChild ("BasicType", xpdlns);
if (bt != null) {
if (bt.getAttributeValue("Type").equals("STRING")) {
return String.class;
}
if (bt.getAttributeValue("Type").equals("FLOAT")) {
return Double.class;
}
if (bt.getAttributeValue("Type").equals("INTEGER")) {
return Long.class;
}
if (bt.getAttributeValue("Type").equals("DATETIME")) {
return Date.class;
}
if (bt.getAttributeValue("Type").equals("BOOLEAN")) {
return Boolean.class;
}
if (bt.getAttributeValue("Type").equals("PERFORMER")) {
return Participant.class;
}
}
Element st = typeElem.getChild ("SchemaType", xpdlns);
if (st != null) {
List sd = st.getChildren();
if (sd.size () == 0) {
return org.w3c.dom.Element.class;
}
SAXEventBufferImpl seb = new SAXEventBufferImpl ();
try {
(new SAXOutputter (seb)).output (sd);
} catch (JDOMException e) {
new IllegalArgumentException ("Cannot convert schema to SAX: "
+ e.getMessage ());
}
seb.pack();
return seb;
}
Element er = typeElem.getChild ("ExternalReference", xpdlns);
if (er != null) {
try {
return new DefaultExternalReference
(makeURI(er.getAttributeValue("location")),
er.getAttributeValue("xref"),
er.getAttributeValue("namespace"));
} catch (URISyntaxException e) {
throw new IllegalArgumentException (e.getMessage ());
}
}
throw new IllegalArgumentException ("Unknown type");
}
/**
* Helper class for retrieving a data type from the process
* definition.<P>
*
* The type information is coded as follows:
* <ul>
* <li>
* if the type is a <code><BasicType></code>, the corresponding
* Java class is returned, with
* {@link de.danet.an.workflow.api.Participant <code>Participant</code>}
* used to denote "PERFORMER".
* </li>
* <li>
* if the type is <code><SchemaType></code>, but no schema is
* specified, <code>org.w3c.dom.Element.class</code> is returned.
* </li>
* <li>
* if the type is <code><SchemaType></code>, and the schema is
* specified, the schema is returned as a
* {@link de.danet.an.workflow.api.SAXEventBuffer
* <code>SAXEventBuffer</code>}.
* </li>
* <li>
* if the type is <code><ExternalReference></code>,
* an <code>ExternalReference</code> is returned.
* </li>
* </ul>
*/
public static class SAXDataTypeHandler extends StackedHandler {
private boolean waitingForSchema = false;
private boolean gotSchema = false;
private SAXEventBufferImpl schemaInfo = null;
/**
* Receive notification of the beginning of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @param a the attributes attached to the element. If there are
* no attributes, it shall be an empty Attributes object.
* @throws SAXException not thrown.
*/
public void startElement
(String uri, String loc, String raw, Attributes a)
throws SAXException {
if (waitingForSchema) {
gotSchema = true;
schemaInfo = new SAXEventBufferImpl ();
getStack().push (schemaInfo);
}
if (loc.equals ("BasicType")) {
String type = a.getValue("Type");
if (type.equals ("STRING")) {
setContextData ("DataType", String.class);
} else if (type.equals ("FLOAT")) {
setContextData ("DataType", Double.class);
} else if (type.equals ("INTEGER")) {
setContextData ("DataType", Long.class);
} else if (type.equals ("DATETIME")) {
setContextData ("DataType", Date.class);
} else if (type.equals ("BOOLEAN")) {
setContextData ("DataType", Boolean.class);
} else if (type.equals ("PERFORMER")) {
setContextData ("DataType", Participant.class);
}
} else if (loc.equals ("SchemaType")) {
waitingForSchema = true;
} else if (loc.equals ("ExternalReference")) {
try {
setContextData
("DataType", new DefaultExternalReference
(makeURI (a.getValue("location")), a.getValue ("xref"),
a.getValue ("namespace")));
} catch (URISyntaxException e) {
// shouldn't happen, has been checked on import
throw new SAXException (e);
}
}
}
/**
* Receive notification of the end of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @throws SAXException not thrown.
*/
public void endElement(String uri, String loc, String raw)
throws SAXException {
if (loc.equals ("SchemaType")) {
if (gotSchema) {
schemaInfo.pack ();
setContextData ("DataType", schemaInfo);
} else {
setContextData ("DataType", org.w3c.dom.Element.class);
}
}
}
}
/**
* Extract the value from a <code><DataType></code> and
* a value representing node.
* @param typeElem the <code><DataType></code> element.
* @param valueElem the value element.
* @return the extracted value which may be <code>null</code>.
* @throws IllegalArgumentException if the value cannot be extracted.
* @deprecated no longer used with SAX initialization
*/
public static Object extractValue (Element typeElem, Element valueElem)
throws IllegalArgumentException {
if (valueElem == null) {
return null;
}
Namespace xpdlns = Namespace.getNamespace (XPDLUtil.XPDL_NS);
Element bt = typeElem.getChild ("BasicType", xpdlns);
if (bt != null) {
try {
if (bt.getAttributeValue("Type").equals("STRING")) {
return valueElem.getText();
}
if (bt.getAttributeValue("Type").equals("FLOAT")) {
return new Double (valueElem.getText());
}
if (bt.getAttributeValue("Type").equals("INTEGER")) {
return new Long (valueElem.getText());
}
if (bt.getAttributeValue("Type").equals("DATETIME")) {
return XMLUtil.parseXsdDateTime (valueElem.getText());
}
if (bt.getAttributeValue("Type").equals("BOOLEAN")) {
return new Boolean (valueElem.getText());
}
if (bt.getAttributeValue("Type").equals("PERFORMER")) {
return valueElem.getText();
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException
("Cannot convert to type: " + e.getMessage ());
} catch (ParseException e) {
throw new IllegalArgumentException
("Cannot convert to type: " + e.getMessage ());
}
}
if (typeElem.getChild ("SchemaType", xpdlns) != null
|| typeElem.getChild ("ExternalReference", xpdlns) != null) {
List cl = valueElem.getChildren();
if (cl.size () == 1) {
return ((Element)cl.get(0)).clone ();
}
if (cl.size () > 0) {
List res = new ArrayList ();
for (Iterator i = cl.iterator (); i.hasNext ();) {
Element el = (Element)i.next ();
res.add (el.clone());
}
return res;
}
try {
SAXParserFactory spf = SAXParserFactory.newInstance ();
spf.setValidating (false);
spf.setNamespaceAware (true);
spf.setFeature
("http://xml.org/sax/features/namespace-prefixes", true);
XMLReader xr = null;
try {
spf.setFeature
("http://xml.org/sax/features/xmlns-uris", true);
xr = spf.newSAXParser().getXMLReader();
} catch (SAXException e) {
xr = new XmlnsUrisPatcher
(spf.newSAXParser().getXMLReader());
}
SAXContentBuffer seb = new SAXEventBufferImpl ();
XMLFilterImpl filter = new XMLFilterImpl () {
private int level = 0;
public void startElement
(String uri, String localName, String qName,
Attributes atts) throws SAXException {
if (level > 0) {
super.startElement (uri,localName,qName,atts);
}
level += 1;
}
public void endElement
(String uri, String localName, String qName)
throws SAXException{
level -= 1;
if (level > 0) {
super.endElement (uri, localName, qName);
}
}
};
filter.setContentHandler (seb);
xr.setContentHandler (filter);
xr.parse (new InputSource
(new StringReader
("<temporary-root>" + valueElem.getText()
+ "</temporary-root>")));
seb.pack();
return seb;
} catch (ParserConfigurationException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
} catch (SAXException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
} catch (IOException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
}
}
throw new IllegalArgumentException ("Unknown data type");
}
/**
* Check if the given object is one of the possible
* representations of XML types used in WfMOpen.
*
* @param type the object representing the type
* @return <code>true</code> if the argument represents an XML type
*/
public static boolean isXMLType (Object type) {
return (type instanceof SAXEventBuffer)
|| type.equals(org.w3c.dom.Element.class)
|| ((type instanceof ExternalReference)
&& !isJavaType((ExternalReference)type));
}
/**
* Retrieve a duration from the given String. The result can be a
* {@link de.danet.an.util.Duration <code>Duration</code>} if the
* duration is specified as a delta or a {@link java.util.Date
* <code>Date</code>} if the duration is specified as an absolute
* value.<P>
*
* Attempts to parse the string are made in the following order:
* <ol>
* <li>
* As XSD duration (see {@link de.danet.an.util.XMLUtil#parseXsdDuration
* <code>parseXsdDuration</code>}).
* </li>
* <li>
* As an XSD dateTime (see {@link
* de.danet.an.util.XMLUtil#parseXsdDateTime
* <code>parseXsdDateTime</code>}).
* </li>
* <li>
* As duration specification as described in
* {@link de.danet.an.util.Duration <code>Duration</code>}.
* </li>
* <li>
* As a date time specification in the formats "d MMM yy HH:mm:ss z"
* and "d MMM yy HH:mm z" (see {@link java.text.SimpleDateFormat
* <code>SimpleDateFormat</code>}). An optionally leading "EEE, "
* is ignored. This includes RFC822 conformant date time specifications.
* </li>
* </ol>
*
* @param s the string to be parsed
* @param e an evaluator to be passed to {@link Duration#parse
* (String,ValueEvaluator) <code>Duration.parse</code>}
* @return the result
* @throws ParseException if the string cannot be parsed
*/
public static Object parseDuration (String s, ValueEvaluator e)
throws ParseException {
try {
return XMLUtil.parseXsdDuration(s);
} catch (ParseException e1) {
try {
return XMLUtil.parseXsdDateTime(s);
} catch (ParseException e2) {
try {
return Duration.parse(s, e);
} catch (ParseException e3) {
int c = s.indexOf (',');
if (c >= 0) {
s = s.substring (c + 1);
}
try {
return ((DateFormat)dfRfc822plus.clone()).parse (s);
} catch (ParseException e4) {
try {
return ((DateFormat)dfRfc822.clone()).parse (s);
} catch (ParseException e5) {
throw new ParseException
("Invalid duration: " + s, 0);
}
}
}
}
}
}
/**
* Retrieve a duration from the given String. Handles numbers as
* values in duration parsing only.
*
* @param s the string to be parsed
* @return the result
* @throws ParseException if the string cannot be parsed
* @see #parseDuration(String,ValueEvaluator)
*/
public static Object parseDuration (String s)
throws ParseException {
return parseDuration (s, null);
}
/**
* Create an URI, adding the java scheme if necessary.
*/
private static URI makeURI (String uri) throws URISyntaxException {
URI res = new URI (uri);
if (res.getScheme() == null && res.getFragment() == null) {
res = new URI ("java", res.getSchemeSpecificPart(), null);
}
return res;
}
/**
* Checks if the external reference is a Java type.
* @param extRef the external reference.
* @return <code>true</code> if the extzernal reference
* describes a Java type.
*/
public static boolean isJavaType (ExternalReference extRef) {
return extRef.location().getScheme() != null
&& extRef.location().getScheme().equals("java");
}
/**
* Retrieves the Java type from the external reference.
* @param extRef the external reference.
* @return the Java type described by this external reference
* @throws IllegalStateException if this external reference does not
* describe a Java type
*/
public static Class getJavaType (ExternalReference extRef)
throws ClassNotFoundException {
if (!isJavaType(extRef)) {
throw new IllegalStateException ("Not a Java type");
}
return Class.forName
(extRef.location().getSchemeSpecificPart(), true,
Thread.currentThread().getContextClassLoader());
}
}