View Javadoc

1   /*
2    * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.xml.parse;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.Reader;
23  import java.lang.ref.SoftReference;
24  import java.util.Collections;
25  import java.util.EmptyStackException;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Stack;
29  import java.util.concurrent.locks.Lock;
30  import java.util.concurrent.locks.ReentrantLock;
31  
32  import javax.xml.parsers.DocumentBuilder;
33  import javax.xml.parsers.DocumentBuilderFactory;
34  import javax.xml.parsers.ParserConfigurationException;
35  import javax.xml.validation.Schema;
36  
37  import org.opensaml.xml.Configuration;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  import org.w3c.dom.DOMImplementation;
41  import org.w3c.dom.Document;
42  import org.xml.sax.EntityResolver;
43  import org.xml.sax.ErrorHandler;
44  import org.xml.sax.InputSource;
45  import org.xml.sax.SAXException;
46  
47  /**
48   * A pool of JAXP 1.3 {@link DocumentBuilder}s.
49   * 
50   * Builder retrieved from this pool should be returned to the pool with the method
51   * {@link #returnBuilder(DocumentBuilder)}. Builders checked out prior to a change in the pool's properties will not be
52   * effected by the change and will be appropriately dealt with when they are returned.
53   * 
54   * If a the pool reaches its max size and another request for a builder is made behavior is dependent upon
55   * {@link #getCreateBuildersAtPoolLimit()}. If this returns the true then a new builder will be created and returned
56   * but will be discarded when it is returned. If it returns false a builder will not be created and null will be
57   * returned.
58   * 
59   * References to builders are kept by way of {@link SoftReference} so that the garbage collector may reap the builders
60   * if the system is running out of memory.
61   */
62  public class BasicParserPool implements ParserPool {
63  
64      /** Class logger. */
65      private final Logger log = LoggerFactory.getLogger(BasicParserPool.class);
66  
67      /** Current version of the pool. */
68      private long poolVersion;
69  
70      /** Create a new builder when the pool size is reached. Default value: true */
71      private boolean createBuildersAtPoolLimit;
72  
73      /** Whether a change has been made to the builder configuration but has not yet been applied. */
74      private boolean dirtyBuilderConfiguration;
75  
76      /** Factory used to create new builders. */
77      private DocumentBuilderFactory builderFactory;
78  
79      /** Cache of document builders. */
80      private Stack<SoftReference<DocumentBuilder>> builderPool;
81  
82      /** Lock used when we're performing operations that remove items from the pool. */
83      private Lock builderPoolLock;
84  
85      /** Max number of builders allowed in the pool. Default value: 5 */
86      private int maxPoolSize;
87  
88      /** Builder attributes. */
89      private Map<String, Object> builderAttributes;
90  
91      /** Whether the builders are coalescing. Default value: true */
92      private boolean coalescing;
93  
94      /** Whether the builders expand entity references. Default value: true */
95      private boolean expandEntityReferences;
96  
97      /** Builder features. */
98      private Map<String, Boolean> builderFeatures;
99  
100     /** Whether the builders ignore comments. Default value: true */
101     private boolean ignoreComments;
102 
103     /** Whether the builders ignore element content whitespace. Default value: true */
104     private boolean ignoreElementContentWhitespace;
105 
106     /** Whether the builders are namespace aware. Default value: true */
107     private boolean namespaceAware;
108 
109     /** Schema used to validate parsed content. */
110     private Schema schema;
111 
112     /** Whether the builder should validate. Default value: false */
113     private boolean dtdValidating;
114 
115     /** Whether the builders are XInclude aware. Default value: false */
116     private boolean xincludeAware;
117 
118     /** Entity resolver used by builders. */
119     private EntityResolver entityResolver;
120 
121     /** Error handler used by builders. */
122     private ErrorHandler errorHandler;
123 
124     /** Constructor. */
125     public BasicParserPool() {
126         Configuration.validateNonSunJAXP();
127         maxPoolSize = 5;
128         builderPool = new Stack<SoftReference<DocumentBuilder>>();
129         builderPoolLock = new ReentrantLock(true);
130         builderAttributes = new HashMap<String, Object>();
131         coalescing = true;
132         expandEntityReferences = true;
133         builderFeatures = new HashMap<String, Boolean>();
134         ignoreComments = true;
135         ignoreElementContentWhitespace = true;
136         namespaceAware = true;
137         schema = null;
138         dtdValidating = false;
139         xincludeAware = false;
140         errorHandler = new LoggingErrorHandler(log);
141 
142         try {
143             dirtyBuilderConfiguration = true;
144             initializePool();
145         } catch (XMLParserException e) {
146             // default settings, no parsing exception
147         }
148     }
149 
150     /** {@inheritDoc} */
151     public DocumentBuilder getBuilder() throws XMLParserException {
152         DocumentBuilder builder = null;
153 
154         try {
155             if (dirtyBuilderConfiguration) {
156                 initializePool();
157             }
158             builder = builderPool.pop().get();
159         } catch (EmptyStackException e) {
160             // we don't take care of this here because we do the same thing whether
161             // we get this exception or if the builder is null because its was
162             // garbage collected is the same (we're using soft references, remember?)
163         }
164 
165         if (builder == null) {
166             if (builderPool.size() < maxPoolSize || createBuildersAtPoolLimit) {
167                 builder = createBuilder();
168             }
169         }
170 
171         if (builder != null) {
172             return new DocumentBuilderProxy(builder, this);
173         }
174 
175         return null;
176     }
177 
178     /** {@inheritDoc} */
179     public void returnBuilder(DocumentBuilder builder) {
180         if (!(builder instanceof DocumentBuilderProxy)) {
181             return;
182         }
183 
184         DocumentBuilderProxy proxiedBuilder = (DocumentBuilderProxy) builder;
185         if (proxiedBuilder.getOwningPool() != this && proxiedBuilder.getPoolVersion() != poolVersion) {
186             return;
187         }
188 
189         DocumentBuilder unwrappedBuilder = proxiedBuilder.getProxiedBuilder();
190         unwrappedBuilder.reset();
191         SoftReference<DocumentBuilder> builderReference = new SoftReference<DocumentBuilder>(unwrappedBuilder);
192 
193         if (builderPool.size() < maxPoolSize) {
194             builderPool.push(builderReference);
195         }
196     }
197 
198     /** {@inheritDoc} */
199     public Document newDocument() throws XMLParserException {
200         DocumentBuilder builder = getBuilder();
201         Document document = builder.newDocument();
202         returnBuilder(builder);
203         return document;
204     }
205 
206     /** {@inheritDoc} */
207     public Document parse(InputStream input) throws XMLParserException {
208         DocumentBuilder builder = getBuilder();
209         try {
210             Document document = builder.parse(input);
211             return document;
212         } catch (SAXException e) {
213             throw new XMLParserException("Invalid XML", e);
214         } catch (IOException e) {
215             throw new XMLParserException("Unable to read XML from input stream", e);
216         } finally {
217             returnBuilder(builder);
218         }
219     }
220 
221     /** {@inheritDoc} */
222     public Document parse(Reader input) throws XMLParserException {
223         DocumentBuilder builder = getBuilder();
224         try {
225             Document document = builder.parse(new InputSource(input));
226             return document;
227         } catch (SAXException e) {
228             throw new XMLParserException("Invalid XML", e);
229         } catch (IOException e) {
230             throw new XMLParserException("Unable to read XML from input stream", e);
231         } finally {
232             returnBuilder(builder);
233         }
234     }
235 
236     /**
237      * Gets the max number of builders the pool will hold.
238      * 
239      * @return max number of builders the pool will hold
240      */
241     public int getMaxPoolSize() {
242         return maxPoolSize;
243     }
244 
245     /**
246      * Sets the max number of builders the pool will hold.
247      * 
248      * @param newSize max number of builders the pool will hold
249      */
250     public void setMaxPoolSize(int newSize) {
251         maxPoolSize = newSize;
252     }
253 
254     /**
255      * Gets whether new builders will be created when the max pool size is reached.
256      * 
257      * @return whether new builders will be created when the max pool size is reached
258      */
259     public boolean getCreateBuildersAtPoolLimit() {
260         return createBuildersAtPoolLimit;
261     }
262 
263     /**
264      * Sets whether new builders will be created when the max pool size is reached.
265      * 
266      * @param createBuilders whether new builders will be created when the max pool size is reached
267      */
268     public void setCreateBuildersAtPoolLimit(boolean createBuilders) {
269         createBuildersAtPoolLimit = createBuilders;
270     }
271 
272     /**
273      * Gets the builder attributes used when creating builders. This collection is unmodifiable.
274      * 
275      * @return builder attributes used when creating builders
276      */
277     public Map<String, Object> getBuilderAttributes() {
278         return Collections.unmodifiableMap(builderAttributes);
279     }
280 
281     /**
282      * Sets the builder attributes used when creating builders.
283      * 
284      * @param newAttributes builder attributes used when creating builders
285      */
286     public void setBuilderAttributes(Map<String, Object> newAttributes) {
287         builderAttributes = newAttributes;
288         dirtyBuilderConfiguration = true;
289     }
290 
291     /**
292      * Gets whether the builders are coalescing.
293      * 
294      * @return whether the builders are coalescing
295      */
296     public boolean isCoalescing() {
297         return coalescing;
298     }
299 
300     /**
301      * Sets whether the builders are coalescing.
302      * 
303      * @param isCoalescing whether the builders are coalescing
304      */
305     public void setCoalescing(boolean isCoalescing) {
306         coalescing = isCoalescing;
307         dirtyBuilderConfiguration = true;
308     }
309 
310     /**
311      * Gets whether builders expand entity references.
312      * 
313      * @return whether builders expand entity references
314      */
315     public boolean isExpandEntityReferences() {
316         return expandEntityReferences;
317     }
318 
319     /**
320      * Sets whether builders expand entity references.
321      * 
322      * @param expand whether builders expand entity references
323      */
324     public void setExpandEntityReferences(boolean expand) {
325         expandEntityReferences = expand;
326         dirtyBuilderConfiguration = true;
327     }
328 
329     /**
330      * Gets the builders' features. This collection is unmodifiable.
331      * 
332      * @return the builders' features
333      */
334     public Map<String, Boolean> getBuilderFeatures() {
335         return Collections.unmodifiableMap(builderFeatures);
336     }
337 
338     /**
339      * Sets the the builders' features.
340      * 
341      * @param newFeatures the builders' features
342      */
343     public void setBuilderFeatures(Map<String, Boolean> newFeatures) {
344         builderFeatures = newFeatures;
345         dirtyBuilderConfiguration = true;
346     }
347 
348     /**
349      * Gets whether the builders ignore comments.
350      * 
351      * @return whether the builders ignore comments
352      */
353     public boolean getIgnoreComments() {
354         return ignoreComments;
355     }
356 
357     /**
358      * Sets whether the builders ignore comments.
359      * 
360      * @param ignore The ignoreComments to set.
361      */
362     public void setIgnoreComments(boolean ignore) {
363         ignoreComments = ignore;
364         dirtyBuilderConfiguration = true;
365     }
366 
367     /**
368      * Get whether the builders ignore element content whitespace.
369      * 
370      * @return whether the builders ignore element content whitespace
371      */
372     public boolean isIgnoreElementContentWhitespace() {
373         return ignoreElementContentWhitespace;
374     }
375 
376     /**
377      * Sets whether the builders ignore element content whitespace.
378      * 
379      * @param ignore whether the builders ignore element content whitespace
380      */
381     public void setIgnoreElementContentWhitespace(boolean ignore) {
382         ignoreElementContentWhitespace = ignore;
383         dirtyBuilderConfiguration = true;
384     }
385 
386     /**
387      * Gets whether the builders are namespace aware.
388      * 
389      * @return whether the builders are namespace aware
390      */
391     public boolean isNamespaceAware() {
392         return namespaceAware;
393     }
394 
395     /**
396      * Sets whether the builders are namespace aware.
397      * 
398      * @param isNamespaceAware whether the builders are namespace aware
399      */
400     public void setNamespaceAware(boolean isNamespaceAware) {
401         namespaceAware = isNamespaceAware;
402         dirtyBuilderConfiguration = true;
403     }
404 
405     /** {@inheritDoc} */
406     public Schema getSchema() {
407         return schema;
408     }
409 
410     /** {@inheritDoc} */
411     public void setSchema(Schema newSchema) {
412         schema = newSchema;
413         if (schema != null) {
414             setNamespaceAware(true);
415             builderAttributes.remove("http://java.sun.com/xml/jaxp/properties/schemaSource");
416             builderAttributes.remove("http://java.sun.com/xml/jaxp/properties/schemaLanguage");
417         }
418 
419         dirtyBuilderConfiguration = true;
420     }
421 
422     /**
423      * Gets whether the builders are validating.
424      * 
425      * @return whether the builders are validating
426      */
427     public boolean isDTDValidating() {
428         return dtdValidating;
429     }
430 
431     /**
432      * Sets whether the builders are validating.
433      * 
434      * @param isValidating whether the builders are validating
435      */
436     public void setDTDValidating(boolean isValidating) {
437         dtdValidating = isValidating;
438         dirtyBuilderConfiguration = true;
439     }
440 
441     /**
442      * Gets whether the builders are XInclude aware.
443      * 
444      * @return whether the builders are XInclude aware
445      */
446     public boolean isXincludeAware() {
447         return xincludeAware;
448     }
449 
450     /**
451      * Sets whether the builders are XInclude aware.
452      * 
453      * @param isXIncludeAware whether the builders are XInclude aware
454      */
455     public void setXincludeAware(boolean isXIncludeAware) {
456         xincludeAware = isXIncludeAware;
457         dirtyBuilderConfiguration = true;
458     }
459 
460     /**
461      * Gets the current pool version.
462      * 
463      * @return current pool version
464      */
465     protected long getPoolVersion() {
466         return poolVersion;
467     }
468 
469     /**
470      * Initializes the pool with a new set of configuration options.
471      * 
472      * @throws XMLParserException thrown if there is a problem initialzing the pool
473      */
474     protected synchronized void initializePool() throws XMLParserException {
475         if (!dirtyBuilderConfiguration) {
476             // in case the pool was initialized by some other thread
477             return;
478         }
479 
480         try {
481             DocumentBuilderFactory newFactory = DocumentBuilderFactory.newInstance();
482 
483             for (Map.Entry<String, Object> attribute : builderAttributes.entrySet()) {
484                 newFactory.setAttribute(attribute.getKey(), attribute.getValue());
485             }
486 
487             for (Map.Entry<String, Boolean> feature : builderFeatures.entrySet()) {
488                 if (feature.getKey() != null) {
489                     newFactory.setFeature(feature.getKey(), feature.getValue().booleanValue());
490                 }
491             }
492 
493             newFactory.setCoalescing(coalescing);
494             newFactory.setExpandEntityReferences(expandEntityReferences);
495             newFactory.setIgnoringComments(ignoreComments);
496             newFactory.setIgnoringElementContentWhitespace(ignoreElementContentWhitespace);
497             newFactory.setNamespaceAware(namespaceAware);
498             newFactory.setSchema(schema);
499             newFactory.setValidating(dtdValidating);
500             newFactory.setXIncludeAware(xincludeAware);
501 
502             synchronized (this) {
503                 poolVersion++;
504                 dirtyBuilderConfiguration = false;
505                 builderFactory = newFactory;
506                 builderPool.clear();
507             }
508         } catch (ParserConfigurationException e) {
509             throw new XMLParserException("Unable to configure builder factory", e);
510         }
511     }
512 
513     /**
514      * Creates a new document builder.
515      * 
516      * @return newly created document builder
517      * 
518      * @throws XMLParserException thrown if their is a configuration error with the builder factory
519      */
520     protected DocumentBuilder createBuilder() throws XMLParserException {
521         try {
522             DocumentBuilder builder = builderFactory.newDocumentBuilder();
523 
524             if (entityResolver != null) {
525                 builder.setEntityResolver(entityResolver);
526             }
527 
528             if (errorHandler != null) {
529                 builder.setErrorHandler(errorHandler);
530             }
531 
532             return builder;
533         } catch (ParserConfigurationException e) {
534             log.error("Unable to create new document builder", e);
535             throw new XMLParserException("Unable to create new document builder", e);
536         }
537     }
538 
539     /**
540      * A proxy that prevents the manages document builders retrieved from the parser pool.
541      */
542     protected class DocumentBuilderProxy extends DocumentBuilder {
543 
544         /** Builder being proxied. */
545         private DocumentBuilder builder;
546 
547         /** Pool that owns this parser. */
548         private ParserPool owningPool;
549 
550         /** Version of the pool when this proxy was created. */
551         private long owningPoolVersion;
552 
553         /**
554          * Constructor.
555          * 
556          * @param target document builder to proxy
557          * @param owner the owning pool
558          */
559         public DocumentBuilderProxy(DocumentBuilder target, BasicParserPool owner) {
560             owningPoolVersion = owner.getPoolVersion();
561             owningPool = owner;
562             builder = target;
563         }
564 
565         /** {@inheritDoc} */
566         public DOMImplementation getDOMImplementation() {
567             return builder.getDOMImplementation();
568         }
569 
570         /** {@inheritDoc} */
571         public Schema getSchema() {
572             return builder.getSchema();
573         }
574 
575         /** {@inheritDoc} */
576         public boolean isNamespaceAware() {
577             return builder.isNamespaceAware();
578         }
579 
580         /** {@inheritDoc} */
581         public boolean isValidating() {
582             return builder.isValidating();
583         }
584 
585         /** {@inheritDoc} */
586         public boolean isXIncludeAware() {
587             return builder.isXIncludeAware();
588         }
589 
590         /** {@inheritDoc} */
591         public Document newDocument() {
592             return builder.newDocument();
593         }
594 
595         /** {@inheritDoc} */
596         public Document parse(File f) throws SAXException, IOException {
597             return builder.parse(f);
598         }
599 
600         /** {@inheritDoc} */
601         public Document parse(InputSource is) throws SAXException, IOException {
602             return builder.parse(is);
603         }
604 
605         /** {@inheritDoc} */
606         public Document parse(InputStream is) throws SAXException, IOException {
607             return builder.parse(is);
608         }
609 
610         /** {@inheritDoc} */
611         public Document parse(InputStream is, String systemId) throws SAXException, IOException {
612             return builder.parse(is, systemId);
613         }
614 
615         /** {@inheritDoc} */
616         public Document parse(String uri) throws SAXException, IOException {
617             return builder.parse(uri);
618         }
619 
620         /** {@inheritDoc} */
621         public void reset() {
622             // ignore, entity resolver and error handler can't be changed
623         }
624 
625         /** {@inheritDoc} */
626         public void setEntityResolver(EntityResolver er) {
627             return;
628         }
629 
630         /** {@inheritDoc} */
631         public void setErrorHandler(ErrorHandler eh) {
632             return;
633         }
634 
635         /**
636          * Gets the pool that owns this parser.
637          * 
638          * @return pool that owns this parser
639          */
640         protected ParserPool getOwningPool() {
641             return owningPool;
642         }
643 
644         /**
645          * Gets the version of the pool that owns this parser at the time of the proxy's creation.
646          * 
647          * @return version of the pool that owns this parser at the time of the proxy's creation
648          */
649         protected long getPoolVersion() {
650             return owningPoolVersion;
651         }
652 
653         /**
654          * Gets the proxied document builder.
655          * 
656          * @return proxied document builder
657          */
658         protected DocumentBuilder getProxiedBuilder() {
659             return builder;
660         }
661 
662         /** {@inheritDoc} */
663         protected void finalize() throws Throwable {
664             super.finalize();
665             owningPool.returnBuilder(this);
666         }
667     }
668 }