1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.Map;
26 import java.util.Stack;
27
28 import javax.xml.parsers.DocumentBuilder;
29 import javax.xml.parsers.DocumentBuilderFactory;
30 import javax.xml.parsers.ParserConfigurationException;
31 import javax.xml.validation.Schema;
32
33 import org.opensaml.xml.Configuration;
34 import org.opensaml.xml.util.LazyMap;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.w3c.dom.DOMImplementation;
38 import org.w3c.dom.Document;
39 import org.xml.sax.EntityResolver;
40 import org.xml.sax.ErrorHandler;
41 import org.xml.sax.InputSource;
42 import org.xml.sax.SAXException;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public class BasicParserPool implements ParserPool {
65
66
67 private final Logger log = LoggerFactory.getLogger(BasicParserPool.class);
68
69
70 private long poolVersion;
71
72
73 private boolean dirtyBuilderConfiguration;
74
75
76 private DocumentBuilderFactory builderFactory;
77
78
79 private Stack<SoftReference<DocumentBuilder>> builderPool;
80
81
82 private int maxPoolSize;
83
84
85 private Map<String, Object> builderAttributes;
86
87
88 private boolean coalescing;
89
90
91 private boolean expandEntityReferences;
92
93
94 private Map<String, Boolean> builderFeatures;
95
96
97 private boolean ignoreComments;
98
99
100 private boolean ignoreElementContentWhitespace;
101
102
103 private boolean namespaceAware;
104
105
106 private Schema schema;
107
108
109 private boolean dtdValidating;
110
111
112 private boolean xincludeAware;
113
114
115 private EntityResolver entityResolver;
116
117
118 private ErrorHandler errorHandler;
119
120
121 public BasicParserPool() {
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
141 }
142 }
143
144
145 public DocumentBuilder getBuilder() throws XMLParserException {
146 DocumentBuilder builder = null;
147 long version = 0;
148
149 if (dirtyBuilderConfiguration) {
150 initializePool();
151 }
152
153 synchronized(this) {
154 version = getPoolVersion();
155 if (!builderPool.isEmpty()) {
156 builder = builderPool.pop().get();
157 }
158
159
160 if (builder == null) {
161 builder = createBuilder();
162 }
163 }
164
165 if (builder != null) {
166 return new DocumentBuilderProxy(builder, this, version);
167 }
168
169 return null;
170 }
171
172
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) {
180 return;
181 }
182
183 synchronized (this) {
184 if (proxiedBuilder.isReturned()) {
185 return;
186 }
187
188 if (proxiedBuilder.getPoolVersion() != poolVersion) {
189 return;
190 }
191
192 DocumentBuilder unwrappedBuilder = proxiedBuilder.getProxiedBuilder();
193 unwrappedBuilder.reset();
194 SoftReference<DocumentBuilder> builderReference = new SoftReference<DocumentBuilder>(unwrappedBuilder);
195
196 if (builderPool.size() < maxPoolSize) {
197 proxiedBuilder.setReturned(true);
198 builderPool.push(builderReference);
199 }
200 }
201 }
202
203
204 public Document newDocument() throws XMLParserException {
205 DocumentBuilder builder = getBuilder();
206 Document document = builder.newDocument();
207 returnBuilder(builder);
208 return document;
209 }
210
211
212 public Document parse(InputStream input) throws XMLParserException {
213 DocumentBuilder builder = getBuilder();
214 try {
215 Document document = builder.parse(input);
216 return document;
217 } catch (SAXException e) {
218 throw new XMLParserException("Invalid XML", e);
219 } catch (IOException e) {
220 throw new XMLParserException("Unable to read XML from input stream", e);
221 } finally {
222 returnBuilder(builder);
223 }
224 }
225
226
227 public Document parse(Reader input) throws XMLParserException {
228 DocumentBuilder builder = getBuilder();
229 try {
230 Document document = builder.parse(new InputSource(input));
231 return document;
232 } catch (SAXException e) {
233 throw new XMLParserException("Invalid XML", e);
234 } catch (IOException e) {
235 throw new XMLParserException("Unable to read XML from input stream", e);
236 } finally {
237 returnBuilder(builder);
238 }
239 }
240
241
242
243
244
245
246 public int getMaxPoolSize() {
247 return maxPoolSize;
248 }
249
250
251
252
253
254
255 public void setMaxPoolSize(int newSize) {
256 maxPoolSize = newSize;
257 }
258
259
260
261
262
263
264
265
266
267
268 public boolean getCreateBuildersAtPoolLimit() {
269 return true;
270 }
271
272
273
274
275
276
277
278
279
280
281 public void setCreateBuildersAtPoolLimit(boolean createBuilders) {
282
283 }
284
285
286
287
288
289
290 public Map<String, Object> getBuilderAttributes() {
291 return Collections.unmodifiableMap(builderAttributes);
292 }
293
294
295
296
297
298
299 public synchronized void setBuilderAttributes(Map<String, Object> newAttributes) {
300 builderAttributes = newAttributes;
301 dirtyBuilderConfiguration = true;
302 }
303
304
305
306
307
308
309 public boolean isCoalescing() {
310 return coalescing;
311 }
312
313
314
315
316
317
318 public synchronized void setCoalescing(boolean isCoalescing) {
319 coalescing = isCoalescing;
320 dirtyBuilderConfiguration = true;
321 }
322
323
324
325
326
327
328 public boolean isExpandEntityReferences() {
329 return expandEntityReferences;
330 }
331
332
333
334
335
336
337 public synchronized void setExpandEntityReferences(boolean expand) {
338 expandEntityReferences = expand;
339 dirtyBuilderConfiguration = true;
340 }
341
342
343
344
345
346
347 public Map<String, Boolean> getBuilderFeatures() {
348 return Collections.unmodifiableMap(builderFeatures);
349 }
350
351
352
353
354
355
356 public synchronized void setBuilderFeatures(Map<String, Boolean> newFeatures) {
357 builderFeatures = newFeatures;
358 dirtyBuilderConfiguration = true;
359 }
360
361
362
363
364
365
366 public boolean getIgnoreComments() {
367 return ignoreComments;
368 }
369
370
371
372
373
374
375 public synchronized void setIgnoreComments(boolean ignore) {
376 ignoreComments = ignore;
377 dirtyBuilderConfiguration = true;
378 }
379
380
381
382
383
384
385 public boolean isIgnoreElementContentWhitespace() {
386 return ignoreElementContentWhitespace;
387 }
388
389
390
391
392
393
394 public synchronized void setIgnoreElementContentWhitespace(boolean ignore) {
395 ignoreElementContentWhitespace = ignore;
396 dirtyBuilderConfiguration = true;
397 }
398
399
400
401
402
403
404 public boolean isNamespaceAware() {
405 return namespaceAware;
406 }
407
408
409
410
411
412
413 public synchronized void setNamespaceAware(boolean isNamespaceAware) {
414 namespaceAware = isNamespaceAware;
415 dirtyBuilderConfiguration = true;
416 }
417
418
419 public Schema getSchema() {
420 return schema;
421 }
422
423
424 public synchronized void setSchema(Schema newSchema) {
425 schema = newSchema;
426 if (schema != null) {
427 setNamespaceAware(true);
428 builderAttributes.remove("http://java.sun.com/xml/jaxp/properties/schemaSource");
429 builderAttributes.remove("http://java.sun.com/xml/jaxp/properties/schemaLanguage");
430 }
431
432 dirtyBuilderConfiguration = true;
433 }
434
435
436
437
438
439
440 public boolean isDTDValidating() {
441 return dtdValidating;
442 }
443
444
445
446
447
448
449 public synchronized void setDTDValidating(boolean isValidating) {
450 dtdValidating = isValidating;
451 dirtyBuilderConfiguration = true;
452 }
453
454
455
456
457
458
459 public boolean isXincludeAware() {
460 return xincludeAware;
461 }
462
463
464
465
466
467
468 public synchronized void setXincludeAware(boolean isXIncludeAware) {
469 xincludeAware = isXIncludeAware;
470 dirtyBuilderConfiguration = true;
471 }
472
473
474
475
476
477
478 protected long getPoolVersion() {
479 return poolVersion;
480 }
481
482
483
484
485
486
487 protected int getPoolSize() {
488 return builderPool.size();
489 }
490
491
492
493
494
495
496 protected synchronized void initializePool() throws XMLParserException {
497 if (!dirtyBuilderConfiguration) {
498
499 return;
500 }
501
502 DocumentBuilderFactory newFactory = DocumentBuilderFactory.newInstance();
503 setAttributes(newFactory, builderAttributes);
504 setFeatures(newFactory, builderFeatures);
505 newFactory.setCoalescing(coalescing);
506 newFactory.setExpandEntityReferences(expandEntityReferences);
507 newFactory.setIgnoringComments(ignoreComments);
508 newFactory.setIgnoringElementContentWhitespace(ignoreElementContentWhitespace);
509 newFactory.setNamespaceAware(namespaceAware);
510 newFactory.setSchema(schema);
511 newFactory.setValidating(dtdValidating);
512 newFactory.setXIncludeAware(xincludeAware);
513
514 poolVersion++;
515 dirtyBuilderConfiguration = false;
516 builderFactory = newFactory;
517 builderPool.clear();
518 }
519
520
521
522
523
524
525
526
527 protected void setAttributes(DocumentBuilderFactory factory, Map<String, Object> attributes) {
528 if (attributes == null || attributes.isEmpty()) {
529 return;
530 }
531
532 for (Map.Entry<String, Object> attribute : attributes.entrySet()) {
533 try {
534 log.debug("Setting DocumentBuilderFactory attribute '{}'", attribute.getKey());
535 factory.setAttribute(attribute.getKey(), attribute.getValue());
536 } catch (IllegalArgumentException e) {
537 log.warn("DocumentBuilderFactory attribute '{}' is not supported", attribute.getKey());
538 }
539 }
540 }
541
542
543
544
545
546
547
548 protected void setFeatures(DocumentBuilderFactory factory, Map<String, Boolean> features) {
549 if (features == null || features.isEmpty()) {
550 return;
551 }
552
553 for (Map.Entry<String, Boolean> feature : features.entrySet()) {
554 try {
555 log.debug("Setting DocumentBuilderFactory attribute '{}'", feature.getKey());
556 factory.setFeature(feature.getKey(), feature.getValue());
557 } catch (ParserConfigurationException e) {
558 log.warn("DocumentBuilderFactory feature '{}' is not supported", feature.getKey());
559 }
560 }
561 }
562
563
564
565
566
567
568
569
570 protected DocumentBuilder createBuilder() throws XMLParserException {
571 try {
572 DocumentBuilder builder = builderFactory.newDocumentBuilder();
573
574 if (entityResolver != null) {
575 builder.setEntityResolver(entityResolver);
576 }
577
578 if (errorHandler != null) {
579 builder.setErrorHandler(errorHandler);
580 }
581
582 return builder;
583 } catch (ParserConfigurationException e) {
584 log.error("Unable to create new document builder", e);
585 throw new XMLParserException("Unable to create new document builder", e);
586 }
587 }
588
589
590
591
592 protected class DocumentBuilderProxy extends DocumentBuilder {
593
594
595 private DocumentBuilder builder;
596
597
598 private ParserPool owningPool;
599
600
601 private long owningPoolVersion;
602
603
604 private boolean returned;
605
606
607
608
609
610
611
612
613 public DocumentBuilderProxy(DocumentBuilder target, BasicParserPool owner, long version) {
614 owningPoolVersion = version;
615 owningPool = owner;
616 builder = target;
617 returned = false;
618 }
619
620
621 public DOMImplementation getDOMImplementation() {
622 checkValidState();
623 return builder.getDOMImplementation();
624 }
625
626
627 public Schema getSchema() {
628 checkValidState();
629 return builder.getSchema();
630 }
631
632
633 public boolean isNamespaceAware() {
634 checkValidState();
635 return builder.isNamespaceAware();
636 }
637
638
639 public boolean isValidating() {
640 checkValidState();
641 return builder.isValidating();
642 }
643
644
645 public boolean isXIncludeAware() {
646 checkValidState();
647 return builder.isXIncludeAware();
648 }
649
650
651 public Document newDocument() {
652 checkValidState();
653 return builder.newDocument();
654 }
655
656
657 public Document parse(File f) throws SAXException, IOException {
658 checkValidState();
659 return builder.parse(f);
660 }
661
662
663 public Document parse(InputSource is) throws SAXException, IOException {
664 checkValidState();
665 return builder.parse(is);
666 }
667
668
669 public Document parse(InputStream is) throws SAXException, IOException {
670 checkValidState();
671 return builder.parse(is);
672 }
673
674
675 public Document parse(InputStream is, String systemId) throws SAXException, IOException {
676 checkValidState();
677 return builder.parse(is, systemId);
678 }
679
680
681 public Document parse(String uri) throws SAXException, IOException {
682 checkValidState();
683 return builder.parse(uri);
684 }
685
686
687 public void reset() {
688
689 }
690
691
692 public void setEntityResolver(EntityResolver er) {
693 checkValidState();
694 return;
695 }
696
697
698 public void setErrorHandler(ErrorHandler eh) {
699 checkValidState();
700 return;
701 }
702
703
704
705
706
707
708 protected ParserPool getOwningPool() {
709 return owningPool;
710 }
711
712
713
714
715
716
717 protected long getPoolVersion() {
718 return owningPoolVersion;
719 }
720
721
722
723
724
725
726 protected DocumentBuilder getProxiedBuilder() {
727 return builder;
728 }
729
730
731
732
733
734
735
736 protected boolean isReturned() {
737 return returned;
738 }
739
740
741
742
743
744
745
746 protected void setReturned(boolean isReturned) {
747 this.returned = isReturned;
748 }
749
750
751
752
753
754
755 protected void checkValidState() throws IllegalStateException {
756 if (isReturned()) {
757 throw new IllegalStateException("DocumentBuilderProxy has already been returned to its owning pool");
758 }
759 }
760
761
762 protected void finalize() throws Throwable {
763 super.finalize();
764 owningPool.returnBuilder(this);
765 }
766 }
767 }