Package org.jbpm.pvm.internal.xml

Source Code of org.jbpm.pvm.internal.xml.Parser

/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.pvm.internal.xml;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.jbpm.internal.log.Log;
import org.jbpm.pvm.internal.stream.StreamInput;
import org.jbpm.pvm.internal.util.UrlEntity;
import org.jbpm.pvm.internal.util.XmlUtil;
import org.jbpm.pvm.internal.wire.Descriptor;
import org.jbpm.pvm.internal.wire.descriptor.ArgDescriptor;
import org.jbpm.pvm.internal.wire.xml.WireParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

/** makes typical usage of JAXP more convenient, adds a binding framework,
* entity resolution and error handling.
*
* <h2>Purpose</h2>
* <p>This is a base parser for the common pattern where first JAXP is used
* to parse xml into a Document Object Model (DOM), and then, this DOM is
* examined to build a domain model object. The main purpose of this parser
* is to serve as a base class for implementing such parsers and to provide
* a more convenient API for working with JAXP.
* </p>
*
* <p>A {@link Parser} is a thread safe object.  For each parse operation, a
* new {@link Parse} object is created with method {@link #createParse()}
* Then the parse object is used to specify the input source, execute the
* parse operation and extract the results.
* </p>
*
* <p>{@link Binding}s capture parsing of a certain element type. This way,
* the parser becomes more modular and customizable.
* </p>
*
* <p>{@link Entity Entities} are schema's that specify the grammar of the
* XML files that are parsed by the parser.
* </p>
*
* <h2>API Usage</h2>
* <p>Parsers can be customized by inheritance (that will be covered below),
* but a parser can also be used as is:
* </p>
*
* <pre><i> 1 </i>|   static Parser parser = new Parser();
*<i> 2 </i>|
*<i> 3 </i>|   void someMethod() {
*<i> 4 </i>|     MyDomainObject mdo = (MyDomainObject) parser
*<i> 5 </i>|             .createParse()
*<i> 6 </i>|             .setString(myXmlString)
*<i> 7 </i>|             .execute()
*<i> 8 </i>|             .checkProblems()
*<i> 9 </i>|             .getDocumentObject();
*<i>10 </i>|   }
* </pre>
*
* <p><b>line 1</b> shows that a single parser can be used for all threads as
* the parser is maintained in a static member field.
* </p>
*
* <p><b>line 5</b> shows that a new parse operation is always started with
* the {@link #createParse()} operation.  The {@link Parse} object that is
* returned will maintain all data that is related to that single parse
* operation.
* </p>
*
* <p><b>line 6</b> shows how a simple XML string can be provided as the input
* source for the parse operation.  Alternative methods to specify the input
* source are {@link Parse#setFile(java.io.File)},
* {@link Parse#setInputStream(java.io.InputStream)},
* {@link Parse#setInputSource(InputSource)},
* {@link Parse#setUrl(java.net.URL)} and
* {@link Parse#setStreamSource(StreamInput)}.
* </p>
*
* <p><b>line 7</b> shows how the execution of the parse is performed.  The
* input source will be read, the resulting Document Object Model (DOM) will
* be walked and potentially problems are produced in the parse.
* </p>
*
* <p><b>line 8</b> shows how an exception can be thrown in case of an error.
* The parse execution itself tries to keep parsing as much as possible to
* provide the developer with as much feedback as possible in one parse cycle.
* The {@link Parse#getProblems() problems} are silently captured in the parse
* object.  If an exception is thrown by
* {@link Parse#checkErrors(String)}, it will contain a report of
* all the parsing problems.  Alternatively, the {@link Parse#getProblems() problems
* in the parse object} could be examined directly without the need for an exception.
* </p>
*
* <p><b>line 9</b> shows how the result of the parse operation is extracted
* from the parse object. 
* </p>
*
* <h2 id="binding">Binding</h2>
* <p>Bindings are the link between a certain type of element in your XML document
* and the corresponding java object in your domain model.</p>
*
* <p>A parser can be configured with a set of {@link Binding}s.  Each {@link Binding}
* knows how to transform a dom element of a given tagName to the corresponding Java
* object.  {@link Bindings} has a notion of binding categories.  For example, activities
* and actions can be seen as different categories in jPDL.
* </p>
*
* <p>The purpose of bindings is to make certain elements in the parsing configurable.
* E.g. in jPDL, the main structure of the document is fixed.  But activity types can be
* added dynamically.
* </p>
*
* <p>The current {@link Bindings} implementation only supports matching of an
* element with a {@link Binding} based on tagName.  If you want to take other things
* into account (e.g. when you want to differentiate between elements of the same
* tagName with a different attribute value), you can create a specialized
* {@link Bindings} class.</p>
*
* <p>Bindings are added by tagName, but they have to be looked up by element.  That is
* to support more specialized bindings implementations that match an element with a
* binding on more information then just the tagName.  In that case, a specialized subclass of
* {@link Binding} should be created and the method {@link #getBinding(Element, String)} and
* constructor {@link Bindings#Bindings(Bindings)} should be provided
* with the more specialized matching behaviour.
* </p>
*
* <h2 id="objectstack">Object stack</h2>
* <p>When implementing {@link Binding}s, you might want to make use of the
* contextual object stack that is provided on the {@link Parse}.  The
* {@link Binding} implementations can maintain Java objects on that stack
* that are being created.
* </p>
*
* <p>E.g. you could push the ProcessDefinition element onto the object stack while it
* is being parsed like this:
* </p>
*
* <pre>public class MyProcessBinding implements Binding {
*
*   public Object parse(Element element, Parse parse, Parser parser) {
*     <i>// instantiate the object for this binding</i>
*     MyProcess myProcess = new MyProcess();
*
*     <i>// collect all the child elements of element</i>
*     List<Element> elements = XmlUtil.elements(element);
*
*     <i>// push my processDefinition onto the object stack</i>
*     parse.pushObject(myProcess);
*     try {
*
*       for (Element activityElement: elements) {
*         // parse the child elements with the bindings in category "activity"
*         parseElement(activityElement, parse, "activity");
*       }
*     } finally {
*       // make sure my processDefinition is popped.
*       parse.popObject();
*     }
*     return myProcess;
*   }
* }
* </pre>
*
* <p>Then, activity bindings might access the processDefinition like this:
* </p>
*
* <pre>public class MyNodeBinding implements Binding {
*
*   public Object parse(Element element, Parse parse, Parser parser) {
*     <i>// instantiate the object for this binding</i>
*     MyNode myNode = new MyNode();
*
*     <i>// add the activity to the processDefinition</i>
*     MyProcess myProcess = parse.findObject(MyProcess.class);
*     myProcess.addNode(myNode);
*     myNode.setMyProcess(myProcess);
*
*     return myNode;
*   }
* }
* </pre>
*
* <p>A parser implementation will typically have a static Bindings object that
* is leveraged in all parser objects.   To customize bindings for a such a parser
* be sure to make a deep copy with {@link Bindings#Bindings(Bindings)} before
* you start adding more bindings to the specialized parser.  Otherwise the
* base parser's bindings will be updated as well.
* </p>
*
* <h2 id="buildingcustomparsers">Building custom parsers</h2>
*
* <p>This parser is build for inheritance.  
* Overriding method {@link #parseDocumentElement(Element, Parse)} can be an easy
* way to start writing your own logic on walking the Document Object Model (DOM).
* Such customizations can still be combined with the usage of
* <a href="#binding">bindings</a>.
* </p>
*
* <h2 id="entityresolving">Entity resolving</h2>
* <p>A parser can be configured with a set of entities with the
* {@link #addEntity(String, Entity)} method.  The {@link UrlEntity} has
* a convenience method to build entities from resources
* {@link UrlEntity#UrlEntity(String, ClassLoader)}.
* </p>
*
* <p>When a document builder is created, the default implementation of the
* {@link #setEntityResolver(DocumentBuilder)} will set this parser as the entity resolver.
* The implementation method of {@link EntityResolver} ({@link #resolveEntity(String, String)}
* will use the added {@link Entity}s to try and find a match based on the
* publicId.  If one is found, the {@link Entity} inputSource is returned, otherwise
* the systemId is used.
* </p>
*
* <p>This class is intended to be used with aggregation as well as inheritence.
* </p>
*
* @author Tom Baeyens
*/
public class Parser {

  private static Log log = Log.getLog(Parser.class.getName());

  protected SAXParserFactory saxParserFactory;
  protected String[] schemaResources;
 
  protected DocumentBuilderFactory documentBuilderFactory = null;
 
  protected Bindings bindings = null;
  protected ClassLoader classLoader = null;

  /** the default parser */
  public Parser() {
    initialize();
  }

  /** creates a new Parser with bindings that can be maintained statically in
   * specialized subclasses of Parser. */
  public Parser(Bindings bindings) {
    initialize();
    this.bindings = bindings;
  }

  /** creates a new Parser with bindings and entities that can be maintained statically
   * in specialized subclasses of Parser.
   * @deprecated entities should be replaced by {@link #setSchemaResources(List)} */
  public Parser(Bindings bindings, Map<String, Entity> entities) {
    initialize();
    this.bindings = bindings;
  }
 
  // initialization ///////////////////////////////////////////////////////////

  public void initialize() {
    initializeSaxParserFactory();
    initializeDocumentBuilderFactory();
  }

  public void initializeDocumentBuilderFactory() {
    documentBuilderFactory = DocumentBuilderFactory.newInstance();
    documentBuilderFactory.setNamespaceAware(true);
  }

  public void initializeSaxParserFactory() {
    saxParserFactory = SAXParserFactory.newInstance();
    saxParserFactory.setNamespaceAware(true);
  }

  // document builder methods /////////////////////////////////////////////////

  /** customizable creation of a new document builder.  Used by
   * {@link #buildDom(Parse)}. */
  protected DocumentBuilder createDocumentBuilder(Parse parse) {
    try {
      parse.documentBuilder = documentBuilderFactory.newDocumentBuilder();
    } catch (Exception e) {
      parse.addProblem("couldn't get new document builder", e);
      return null;
    }
    parse.documentBuilder.setErrorHandler(parse);
    return parse.documentBuilder;
  }

  // schema validation ////////////////////////////////////////////////////////
 
  public void setSchemaResources(List<String> resources) {
    saxParserFactory.setValidating(true);
    saxParserFactory.setNamespaceAware(true);

    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    List<String> schemaLocations = new ArrayList<String>(resources.size());
    for (String schemaResource: resources) {
      URL schemaUrl = classLoader.getResource(schemaResource);
      if (schemaUrl!=null) {
        String schemaLocation = schemaUrl.toString();
        log.trace("schema resource found: " + schemaResource);
        schemaLocations.add(schemaLocation);
      } else {
        log.debug("skipping unavailble schema resource: " + schemaResource);
      }
    }
    schemaResources = schemaLocations.toArray(new String[schemaLocations.size()]);
  }

  // bindings /////////////////////////////////////////////////////////////////

  /** the handlers for specific element types */
  public Bindings getBindings() {
    return bindings;
  }

  /** set the handlers for specific element types */
  public void setBindings(Bindings bindings) {
    this.bindings = bindings;
  }

  /** the handler for the given element */
  public Binding getBinding(Element element) {
    return getBinding(element, null);
  }

  /** the handler for the given element limited to a given category */
  public Binding getBinding(Element element, String category) {
    return (bindings!=null ? bindings.getBinding(element, category) : null);
  }

  // runtime parsing methods //////////////////////////////////////////////////
 
  /** main method to start a new parse, check {@link Parse} for specifying
   * input, executing the parse and extracting the results. */
  public Parse createParse() {
    return new Parse(this);
  }

  /** builds a dom from the importedStreamSource and appends the child elements
   * of the document element to the destination element.  Problems are reported
   * in the importingParse. */
  public void importStream(StreamInput importedStreamInput, Element destination, Parse importingParse) {
    try {
      // build the dom of the imported document
      Parse importedParse = createParse();
      importedParse.setStreamSource(importedStreamInput);
      Document importedDocument = buildDom(importedParse);
     
      // loop over all the imported document elements
      Element importedDocumentElement = importedDocument.getDocumentElement();
      for(Element e : XmlUtil.elements(importedDocumentElement)) {
        // import the element into the destination element
        destination.appendChild(destination.getOwnerDocument().importNode(e, true));
      }
     
    } catch (Exception e) {
      importingParse.addProblem("couldn't import "+importedStreamInput, e);
    }
  }

  /** customizable parse execution */
  protected void execute(Parse parse) {
    try {
      if (parse.document==null) {
        parse.document = buildDom(parse);
      }

      // walk the dom tree
      if (parse.document!=null) {
        try {
          // walk the dom tree
          parseDocument(parse.document, parse);

        } catch (Exception e) {
          parse.addProblem("parsing exception: "+e.getMessage(), e);
        }
      }
     
    } finally {
      if (parse.inputStream!=null) {
        try {
          parse.inputStream.close();
        } catch (Exception e) {
          parse.addProblem("couldn't close input stream", e);
        }
      }
    }
  }

  protected Document buildDom(Parse parse) {
    Document document = null;

    try {
      SAXParser saxParser = saxParserFactory.newSAXParser();
      XMLReader xmlReader = saxParser.getXMLReader();
     
      try {
        saxParser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
      } catch (Exception e){
        log.info("couldn't set schema language property", e);
      }

      if (schemaResources!=null) {
        try {
          saxParser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", schemaResources);
        } catch (Exception e){
          log.info("couldn't set schema source property", e);
        }
      }

      try {
        xmlReader.setFeature("http://apache.org/xml/features/validation/dynamic", true);
      } catch (Exception e){
        log.info("couldn't set dynamic validation feature", e);
      }

      DocumentBuilder documentBuilder = createDocumentBuilder(parse);
      document = documentBuilder.getDOMImplementation().createDocument(null, null, null);
      parse.setDocument(document);

      DomBuilder domBuilder = new DomBuilder();
      domBuilder.setDocument(document);

      xmlReader.setContentHandler(domBuilder);
      xmlReader.setErrorHandler(parse);
     
      InputSource inputSource = parse.getInputSource();
      xmlReader.parse(inputSource);

    } catch (Exception e) {
      parse.addProblem("couldn't parse xml document", e);
    }

    return document;
  }


  // Document Object Model walking ////////////////////////////////////////////

  /** start of the DOM walk.
   *
   * This method is used as part of
   * {@link #execute(Parse) the parse execution}.
   *
   * This default implementation behaviour extracts the document element and
   * delegates to {@link #parseDocumentElement(Element, Parse)}.
   *
   * This method can be overridden for customized behaviour.
   *
   * @return the object that is the result from parsing this document. */
  public Object parseDocument(Document document, Parse parse) {
    Object object = parseDocumentElement(document.getDocumentElement(), parse);
    parse.documentObject = object;
    return object;
  }

  /** parses the top level element in the document and produces the object that
   * is the result from the parsing.
   *
   * @return the object that is the result from parsing this document element. */
  public Object parseDocumentElement(Element documentElement, Parse parse) {
    return parseElement(documentElement, parse);
  }

  /** parses an arbitrary element in the document with the first matching
   * binding found using any of the categories.
   *
   * @return the object that is the result from parsing this element. */
  public Object parseElement(Element element, Parse parse) {
    return parseElement(element, parse, null);
  }

  /** parses an arbitrary element in the document based on the bindings in the
   * given category.
   *
   * @param category is the category in which the tagName should be resolved to
   *   a {@link Binding}.  If category is null, all the categories will be
   *   scanned for an appropriate binding in random order.
   *
   * @return the object that is the result from parsing this element. */
  public Object parseElement(Element element, Parse parse, String category) {

    Object object = null;
    String tagName = XmlUtil.getTagLocalName(element);

    Binding binding = getBinding(element, category);

    if (binding!=null) {
      object = binding.parse(element, parse, this);
    } else if (log.isDebugEnabled()) {
      log.debug("no element parser for tag "+tagName+(category!=null ? " in category "+category : " in the default category"));
    }

    return object;
  }

  public List<ArgDescriptor> parseArgs(List<Element> argElements, Parse parse) {
    return parseArgs(argElements, parse, WireParser.CATEGORY_DESCRIPTOR);
  }

  public List<ArgDescriptor> parseArgs(List<Element> argElements, Parse parse, String category) {
    List<ArgDescriptor> args = null;
    if (argElements!=null) {
      if (argElements.size()>0) {
        args = new ArrayList<ArgDescriptor>(argElements.size());
      }
      for (Element argElement: argElements) {
        ArgDescriptor argDescriptor = new ArgDescriptor();
        argDescriptor.setTypeName(XmlUtil.attribute(argElement, "type"));
        Element descriptorElement = XmlUtil.element(argElement);
        if (descriptorElement==null) {
          parse.addProblem("arg must contain exactly one descriptor element out of "+bindings.getTagNames(category)+" as contents:"+XmlUtil.toString((Element) argElement.getParentNode()), argElement);
        } else {
          Descriptor descriptor = (Descriptor) parseElement(descriptorElement, parse, category);
          argDescriptor.setDescriptor(descriptor);
        }
        args.add(argDescriptor);
      }
    }
    return args;
  }
}
TOP

Related Classes of org.jbpm.pvm.internal.xml.Parser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.