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