001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: XPDLUtil.java,v 1.8 2007/05/03 21:58:21 mlipp Exp $
021: *
022: * $Log: XPDLUtil.java,v $
023: * Revision 1.8 2007/05/03 21:58:21 mlipp
024: * Internal refactoring for making better use of local EJBs.
025: *
026: * Revision 1.7 2007/02/27 14:34:17 drmlipp
027: * Some refactoring to reduce cyclic dependencies.
028: *
029: * Revision 1.6 2006/11/20 09:52:37 drmlipp
030: * Fixed problems with using URL class.
031: *
032: * Revision 1.5 2006/11/19 11:16:35 mlipp
033: * Made ExternalReference an interface.
034: *
035: * Revision 1.4 2006/11/17 21:39:44 mlipp
036: * Adapted to Java type support.
037: *
038: * Revision 1.3 2006/11/17 16:16:12 drmlipp
039: * Started support for Java native types.
040: *
041: * Revision 1.2 2006/09/29 12:32:10 drmlipp
042: * Consistently using WfMOpen as projct name now.
043: *
044: * Revision 1.1.1.5 2004/08/18 15:17:39 drmlipp
045: * Update to 1.2
046: *
047: * Revision 1.22 2004/04/29 15:39:31 lipp
048: * Getting on with SAX based initialization.
049: *
050: * Revision 1.21 2004/03/25 14:41:47 lipp
051: * Added possibility to specify actual parameters as XML.
052: *
053: * Revision 1.20 2004/01/19 12:30:58 lipp
054: * SchemaType now supported properly.
055: *
056: * Revision 1.19 2003/10/23 08:50:42 lipp
057: * Fixed comment.
058: *
059: * Revision 1.18 2003/09/25 11:02:10 lipp
060: * Added support for remote resources in evaluation.
061: *
062: * Revision 1.17 2003/09/20 22:57:20 lipp
063: * Added variant for parseDuration.
064: *
065: * Revision 1.16 2003/09/20 19:26:06 lipp
066: * Modified complex time value specification handling.
067: *
068: * Revision 1.15 2003/09/05 12:38:12 lipp
069: * Added duration parsing for XPDL.
070: *
071: * Revision 1.14 2003/07/04 08:08:45 lipp
072: * Started variable performer.
073: *
074: * Revision 1.13 2003/06/27 08:51:44 lipp
075: * Fixed copyright/license information.
076: *
077: * Revision 1.12 2003/06/10 14:46:44 huaiyang
078: * Fix the error in generating sax events.
079: *
080: * Revision 1.11 2003/06/05 21:30:35 lipp
081: * Better handling of initial XML values.
082: *
083: * Revision 1.10 2003/06/03 16:38:56 lipp
084: * Updated to jdom b9.
085: *
086: * Revision 1.9 2003/05/15 07:25:54 lipp
087: * Javadoc fix.
088: *
089: * Revision 1.8 2003/04/25 20:05:32 lipp
090: * Retrieving participants from SAX now.
091: *
092: * Revision 1.7 2003/04/22 16:56:12 lipp
093: * Added missing pack().
094: *
095: * Revision 1.6 2003/04/22 16:36:28 lipp
096: * Handling schema types.
097: *
098: * Revision 1.5 2003/04/22 14:34:57 lipp
099: * Retrieving context signature from SAX.
100: *
101: * Revision 1.4 2003/04/01 11:16:08 lipp
102: * Handling for XML init data added.
103: *
104: * Revision 1.3 2003/03/28 14:42:35 lipp
105: * Moved some code to XPDLUtil.
106: *
107: * Revision 1.2 2003/03/28 12:45:24 lipp
108: * Moved XPDL related constants to XPDLUtil.
109: *
110: * Revision 1.1 2003/03/28 11:41:59 lipp
111: * More changes for data type support.
112: *
113: */
114: package de.danet.an.workflow.util;
115:
116: import java.io.IOException;
117: import java.io.StringReader;
118:
119: import java.util.ArrayList;
120: import java.util.Date;
121: import java.util.Iterator;
122: import java.util.List;
123: import java.util.Locale;
124:
125: import java.net.URI;
126: import java.net.URISyntaxException;
127: import java.rmi.RemoteException;
128: import java.text.DateFormat;
129: import java.text.ParseException;
130: import java.text.SimpleDateFormat;
131:
132: import javax.xml.parsers.ParserConfigurationException;
133: import javax.xml.parsers.SAXParserFactory;
134:
135: import org.jdom.Element;
136: import org.jdom.JDOMException;
137: import org.jdom.Namespace;
138: import org.jdom.output.SAXOutputter;
139: import org.xml.sax.Attributes;
140: import org.xml.sax.InputSource;
141: import org.xml.sax.SAXException;
142: import org.xml.sax.XMLReader;
143: import org.xml.sax.helpers.XMLFilterImpl;
144:
145: import de.danet.an.util.Duration;
146: import de.danet.an.util.Duration.ValueEvaluator;
147: import de.danet.an.util.XMLUtil;
148: import de.danet.an.util.sax.SAXContentBuffer;
149: import de.danet.an.util.sax.StackedHandler;
150: import de.danet.an.util.sax.XmlnsUrisPatcher;
151:
152: import de.danet.an.workflow.api.ExternalReference;
153: import de.danet.an.workflow.api.Participant;
154: import de.danet.an.workflow.api.SAXEventBuffer;
155:
156: /**
157: * This class provides some methods that help handling XPDL.
158: *
159: * @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
160: * @version $Revision: 1.8 $
161: */
162:
163: public class XPDLUtil {
164:
165: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
166: .getLog(XPDLUtil.class);
167:
168: /** The XPDL version used. */
169: public static final String XPDL_VERSION = "WFMC-TC-1025 Version 1.0 (Beta)";
170:
171: /** The URI of the XPDL version used. */
172: public static final String XPDL_NS = "http://www.wfmc.org/2002/XPDL1.0";
173:
174: /** The URI of the supported XPDL extensions. */
175: public static final String XPDL_EXTN_NS = "http://www.an.danet.de/2002/XPDL-Extensions1.0";
176:
177: private static DateFormat dfRfc822plus = new SimpleDateFormat(
178: "d MMM yy HH:mm:ss z", Locale.US);
179: private static DateFormat dfRfc822 = new SimpleDateFormat(
180: "d MMM yy HH:mm z", Locale.US);
181:
182: /**
183: * Extract the Java data type information from a
184: * <code>DataType</code> node. The type information is coded as follows:
185: * <ul>
186: * <li>
187: * if the type is a <code><BasicType></code>, the corresponding
188: * Java class is returned, with
189: * {@link de.danet.an.workflow.api.Participant <code>Participant</code>}
190: * used to denote "PERFORMER".
191: * </li>
192: * <li>
193: * if the type is <code><SchemaType></code>, but no schema is
194: * specified, <code>org.w3c.dom.Element.class</code> is returned.
195: * </li>
196: * <li>
197: * if the type is <code><SchemaType></code>, and the schema is
198: * specified, the schema is returned is returned
199: * (as <code>SAXEventBuffer</code>).
200: * </li>
201: * <li>
202: * if the type is <code><ExternalReference></code>,
203: * an <code>ExternalReference</code> is returned.
204: * </li>
205: * </ul>
206: * @param typeElem a <code><DataType></code> element.
207: * @return the type information as specified above.
208: * @throws IllegalArgumentException if the type is not recognized.
209: */
210: public static Object extractDataType(Element typeElem)
211: throws IllegalArgumentException {
212: Namespace xpdlns = Namespace.getNamespace(XPDL_NS);
213: Element bt = typeElem.getChild("BasicType", xpdlns);
214: if (bt != null) {
215: if (bt.getAttributeValue("Type").equals("STRING")) {
216: return String.class;
217: }
218: if (bt.getAttributeValue("Type").equals("FLOAT")) {
219: return Double.class;
220: }
221: if (bt.getAttributeValue("Type").equals("INTEGER")) {
222: return Long.class;
223: }
224: if (bt.getAttributeValue("Type").equals("DATETIME")) {
225: return Date.class;
226: }
227: if (bt.getAttributeValue("Type").equals("BOOLEAN")) {
228: return Boolean.class;
229: }
230: if (bt.getAttributeValue("Type").equals("PERFORMER")) {
231: return Participant.class;
232: }
233: }
234: Element st = typeElem.getChild("SchemaType", xpdlns);
235: if (st != null) {
236: List sd = st.getChildren();
237: if (sd.size() == 0) {
238: return org.w3c.dom.Element.class;
239: }
240: SAXEventBufferImpl seb = new SAXEventBufferImpl();
241: try {
242: (new SAXOutputter(seb)).output(sd);
243: } catch (JDOMException e) {
244: new IllegalArgumentException(
245: "Cannot convert schema to SAX: "
246: + e.getMessage());
247: }
248: seb.pack();
249: return seb;
250: }
251: Element er = typeElem.getChild("ExternalReference", xpdlns);
252: if (er != null) {
253: try {
254: return new DefaultExternalReference(makeURI(er
255: .getAttributeValue("location")), er
256: .getAttributeValue("xref"), er
257: .getAttributeValue("namespace"));
258: } catch (URISyntaxException e) {
259: throw new IllegalArgumentException(e.getMessage());
260: }
261: }
262: throw new IllegalArgumentException("Unknown type");
263: }
264:
265: /**
266: * Helper class for retrieving a data type from the process
267: * definition.<P>
268: *
269: * The type information is coded as follows:
270: * <ul>
271: * <li>
272: * if the type is a <code><BasicType></code>, the corresponding
273: * Java class is returned, with
274: * {@link de.danet.an.workflow.api.Participant <code>Participant</code>}
275: * used to denote "PERFORMER".
276: * </li>
277: * <li>
278: * if the type is <code><SchemaType></code>, but no schema is
279: * specified, <code>org.w3c.dom.Element.class</code> is returned.
280: * </li>
281: * <li>
282: * if the type is <code><SchemaType></code>, and the schema is
283: * specified, the schema is returned as a
284: * {@link de.danet.an.workflow.api.SAXEventBuffer
285: * <code>SAXEventBuffer</code>}.
286: * </li>
287: * <li>
288: * if the type is <code><ExternalReference></code>,
289: * an <code>ExternalReference</code> is returned.
290: * </li>
291: * </ul>
292: */
293: public static class SAXDataTypeHandler extends StackedHandler {
294:
295: private boolean waitingForSchema = false;
296: private boolean gotSchema = false;
297: private SAXEventBufferImpl schemaInfo = null;
298:
299: /**
300: * Receive notification of the beginning of an element.
301: *
302: * @param uri the Namespace URI, or the empty string if the
303: * element has no Namespace URI or if Namespace processing is not
304: * being performed.
305: * @param loc the local name (without prefix), or the empty string
306: * if Namespace processing is not being performed.
307: * @param raw the raw XML 1.0 name (with prefix), or the empty
308: * string if raw names are not available.
309: * @param a the attributes attached to the element. If there are
310: * no attributes, it shall be an empty Attributes object.
311: * @throws SAXException not thrown.
312: */
313: public void startElement(String uri, String loc, String raw,
314: Attributes a) throws SAXException {
315: if (waitingForSchema) {
316: gotSchema = true;
317: schemaInfo = new SAXEventBufferImpl();
318: getStack().push(schemaInfo);
319: }
320: if (loc.equals("BasicType")) {
321: String type = a.getValue("Type");
322: if (type.equals("STRING")) {
323: setContextData("DataType", String.class);
324: } else if (type.equals("FLOAT")) {
325: setContextData("DataType", Double.class);
326: } else if (type.equals("INTEGER")) {
327: setContextData("DataType", Long.class);
328: } else if (type.equals("DATETIME")) {
329: setContextData("DataType", Date.class);
330: } else if (type.equals("BOOLEAN")) {
331: setContextData("DataType", Boolean.class);
332: } else if (type.equals("PERFORMER")) {
333: setContextData("DataType", Participant.class);
334: }
335: } else if (loc.equals("SchemaType")) {
336: waitingForSchema = true;
337: } else if (loc.equals("ExternalReference")) {
338: try {
339: setContextData("DataType",
340: new DefaultExternalReference(makeURI(a
341: .getValue("location")), a
342: .getValue("xref"), a
343: .getValue("namespace")));
344: } catch (URISyntaxException e) {
345: // shouldn't happen, has been checked on import
346: throw new SAXException(e);
347: }
348: }
349: }
350:
351: /**
352: * Receive notification of the end of an element.
353: *
354: * @param uri the Namespace URI, or the empty string if the
355: * element has no Namespace URI or if Namespace processing is not
356: * being performed.
357: * @param loc the local name (without prefix), or the empty string
358: * if Namespace processing is not being performed.
359: * @param raw the raw XML 1.0 name (with prefix), or the empty
360: * string if raw names are not available.
361: * @throws SAXException not thrown.
362: */
363: public void endElement(String uri, String loc, String raw)
364: throws SAXException {
365: if (loc.equals("SchemaType")) {
366: if (gotSchema) {
367: schemaInfo.pack();
368: setContextData("DataType", schemaInfo);
369: } else {
370: setContextData("DataType",
371: org.w3c.dom.Element.class);
372: }
373: }
374: }
375: }
376:
377: /**
378: * Extract the value from a <code><DataType></code> and
379: * a value representing node.
380: * @param typeElem the <code><DataType></code> element.
381: * @param valueElem the value element.
382: * @return the extracted value which may be <code>null</code>.
383: * @throws IllegalArgumentException if the value cannot be extracted.
384: * @deprecated no longer used with SAX initialization
385: */
386: public static Object extractValue(Element typeElem,
387: Element valueElem) throws IllegalArgumentException {
388: if (valueElem == null) {
389: return null;
390: }
391: Namespace xpdlns = Namespace.getNamespace(XPDLUtil.XPDL_NS);
392: Element bt = typeElem.getChild("BasicType", xpdlns);
393: if (bt != null) {
394: try {
395: if (bt.getAttributeValue("Type").equals("STRING")) {
396: return valueElem.getText();
397: }
398: if (bt.getAttributeValue("Type").equals("FLOAT")) {
399: return new Double(valueElem.getText());
400: }
401: if (bt.getAttributeValue("Type").equals("INTEGER")) {
402: return new Long(valueElem.getText());
403: }
404: if (bt.getAttributeValue("Type").equals("DATETIME")) {
405: return XMLUtil
406: .parseXsdDateTime(valueElem.getText());
407: }
408: if (bt.getAttributeValue("Type").equals("BOOLEAN")) {
409: return new Boolean(valueElem.getText());
410: }
411: if (bt.getAttributeValue("Type").equals("PERFORMER")) {
412: return valueElem.getText();
413: }
414: } catch (NumberFormatException e) {
415: throw new IllegalArgumentException(
416: "Cannot convert to type: " + e.getMessage());
417: } catch (ParseException e) {
418: throw new IllegalArgumentException(
419: "Cannot convert to type: " + e.getMessage());
420: }
421: }
422: if (typeElem.getChild("SchemaType", xpdlns) != null
423: || typeElem.getChild("ExternalReference", xpdlns) != null) {
424: List cl = valueElem.getChildren();
425: if (cl.size() == 1) {
426: return ((Element) cl.get(0)).clone();
427: }
428: if (cl.size() > 0) {
429: List res = new ArrayList();
430: for (Iterator i = cl.iterator(); i.hasNext();) {
431: Element el = (Element) i.next();
432: res.add(el.clone());
433: }
434: return res;
435: }
436: try {
437: SAXParserFactory spf = SAXParserFactory.newInstance();
438: spf.setValidating(false);
439: spf.setNamespaceAware(true);
440: spf
441: .setFeature(
442: "http://xml.org/sax/features/namespace-prefixes",
443: true);
444: XMLReader xr = null;
445: try {
446: spf.setFeature(
447: "http://xml.org/sax/features/xmlns-uris",
448: true);
449: xr = spf.newSAXParser().getXMLReader();
450: } catch (SAXException e) {
451: xr = new XmlnsUrisPatcher(spf.newSAXParser()
452: .getXMLReader());
453: }
454: SAXContentBuffer seb = new SAXEventBufferImpl();
455: XMLFilterImpl filter = new XMLFilterImpl() {
456: private int level = 0;
457:
458: public void startElement(String uri,
459: String localName, String qName,
460: Attributes atts) throws SAXException {
461: if (level > 0) {
462: super .startElement(uri, localName, qName,
463: atts);
464: }
465: level += 1;
466: }
467:
468: public void endElement(String uri,
469: String localName, String qName)
470: throws SAXException {
471: level -= 1;
472: if (level > 0) {
473: super .endElement(uri, localName, qName);
474: }
475: }
476: };
477: filter.setContentHandler(seb);
478: xr.setContentHandler(filter);
479: xr.parse(new InputSource(new StringReader(
480: "<temporary-root>" + valueElem.getText()
481: + "</temporary-root>")));
482: seb.pack();
483: return seb;
484: } catch (ParserConfigurationException e) {
485: throw new IllegalArgumentException(
486: "Error initiliazing schema type: "
487: + e.getMessage());
488: } catch (SAXException e) {
489: throw new IllegalArgumentException(
490: "Error initiliazing schema type: "
491: + e.getMessage());
492: } catch (IOException e) {
493: throw new IllegalArgumentException(
494: "Error initiliazing schema type: "
495: + e.getMessage());
496: }
497: }
498: throw new IllegalArgumentException("Unknown data type");
499: }
500:
501: /**
502: * Check if the given object is one of the possible
503: * representations of XML types used in WfMOpen.
504: *
505: * @param type the object representing the type
506: * @return <code>true</code> if the argument represents an XML type
507: */
508: public static boolean isXMLType(Object type) {
509: return (type instanceof SAXEventBuffer)
510: || type.equals(org.w3c.dom.Element.class)
511: || ((type instanceof ExternalReference) && !isJavaType((ExternalReference) type));
512: }
513:
514: /**
515: * Retrieve a duration from the given String. The result can be a
516: * {@link de.danet.an.util.Duration <code>Duration</code>} if the
517: * duration is specified as a delta or a {@link java.util.Date
518: * <code>Date</code>} if the duration is specified as an absolute
519: * value.<P>
520: *
521: * Attempts to parse the string are made in the following order:
522: * <ol>
523: * <li>
524: * As XSD duration (see {@link de.danet.an.util.XMLUtil#parseXsdDuration
525: * <code>parseXsdDuration</code>}).
526: * </li>
527: * <li>
528: * As an XSD dateTime (see {@link
529: * de.danet.an.util.XMLUtil#parseXsdDateTime
530: * <code>parseXsdDateTime</code>}).
531: * </li>
532: * <li>
533: * As duration specification as described in
534: * {@link de.danet.an.util.Duration <code>Duration</code>}.
535: * </li>
536: * <li>
537: * As a date time specification in the formats "d MMM yy HH:mm:ss z"
538: * and "d MMM yy HH:mm z" (see {@link java.text.SimpleDateFormat
539: * <code>SimpleDateFormat</code>}). An optionally leading "EEE, "
540: * is ignored. This includes RFC822 conformant date time specifications.
541: * </li>
542: * </ol>
543: *
544: * @param s the string to be parsed
545: * @param e an evaluator to be passed to {@link Duration#parse
546: * (String,ValueEvaluator) <code>Duration.parse</code>}
547: * @return the result
548: * @throws ParseException if the string cannot be parsed
549: */
550: public static Object parseDuration(String s, ValueEvaluator e)
551: throws ParseException {
552: try {
553: return XMLUtil.parseXsdDuration(s);
554: } catch (ParseException e1) {
555: try {
556: return XMLUtil.parseXsdDateTime(s);
557: } catch (ParseException e2) {
558: try {
559: return Duration.parse(s, e);
560: } catch (ParseException e3) {
561: int c = s.indexOf(',');
562: if (c >= 0) {
563: s = s.substring(c + 1);
564: }
565: try {
566: return ((DateFormat) dfRfc822plus.clone())
567: .parse(s);
568: } catch (ParseException e4) {
569: try {
570: return ((DateFormat) dfRfc822.clone())
571: .parse(s);
572: } catch (ParseException e5) {
573: throw new ParseException(
574: "Invalid duration: " + s, 0);
575: }
576: }
577: }
578: }
579: }
580: }
581:
582: /**
583: * Retrieve a duration from the given String. Handles numbers as
584: * values in duration parsing only.
585: *
586: * @param s the string to be parsed
587: * @return the result
588: * @throws ParseException if the string cannot be parsed
589: * @see #parseDuration(String,ValueEvaluator)
590: */
591: public static Object parseDuration(String s) throws ParseException {
592: return parseDuration(s, null);
593: }
594:
595: /**
596: * Create an URI, adding the java scheme if necessary.
597: */
598: private static URI makeURI(String uri) throws URISyntaxException {
599: URI res = new URI(uri);
600: if (res.getScheme() == null && res.getFragment() == null) {
601: res = new URI("java", res.getSchemeSpecificPart(), null);
602: }
603: return res;
604: }
605:
606: /**
607: * Checks if the external reference is a Java type.
608: * @param extRef the external reference.
609: * @return <code>true</code> if the extzernal reference
610: * describes a Java type.
611: */
612: public static boolean isJavaType(ExternalReference extRef) {
613: return extRef.location().getScheme() != null
614: && extRef.location().getScheme().equals("java");
615: }
616:
617: /**
618: * Retrieves the Java type from the external reference.
619: * @param extRef the external reference.
620: * @return the Java type described by this external reference
621: * @throws IllegalStateException if this external reference does not
622: * describe a Java type
623: */
624: public static Class getJavaType(ExternalReference extRef)
625: throws ClassNotFoundException {
626: if (!isJavaType(extRef)) {
627: throw new IllegalStateException("Not a Java type");
628: }
629: return Class.forName(extRef.location().getSchemeSpecificPart(),
630: true, Thread.currentThread().getContextClassLoader());
631: }
632: }
|