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