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