ludc
2025-01-16 986aa62ed00bee39363bab41b4eeb8259d446efd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
/*
 * 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;
  }
}