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.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
49
50
51
52
53
54
55
56
57
58
59
60
61
62 public class BasicParserPool implements ParserPool {
63
64
65 private final Logger log = LoggerFactory.getLogger(BasicParserPool.class);
66
67
68 private long poolVersion;
69
70
71 private boolean createBuildersAtPoolLimit;
72
73
74 private boolean dirtyBuilderConfiguration;
75
76
77 private DocumentBuilderFactory builderFactory;
78
79
80 private Stack<SoftReference<DocumentBuilder>> builderPool;
81
82
83 private Lock builderPoolLock;
84
85
86 private int maxPoolSize;
87
88
89 private Map<String, Object> builderAttributes;
90
91
92 private boolean coalescing;
93
94
95 private boolean expandEntityReferences;
96
97
98 private Map<String, Boolean> builderFeatures;
99
100
101 private boolean ignoreComments;
102
103
104 private boolean ignoreElementContentWhitespace;
105
106
107 private boolean namespaceAware;
108
109
110 private Schema schema;
111
112
113 private boolean dtdValidating;
114
115
116 private boolean xincludeAware;
117
118
119 private EntityResolver entityResolver;
120
121
122 private ErrorHandler errorHandler;
123
124
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
147 }
148 }
149
150
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
161
162
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
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
199 public Document newDocument() throws XMLParserException {
200 DocumentBuilder builder = getBuilder();
201 Document document = builder.newDocument();
202 returnBuilder(builder);
203 return document;
204 }
205
206
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
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
238
239
240
241 public int getMaxPoolSize() {
242 return maxPoolSize;
243 }
244
245
246
247
248
249
250 public void setMaxPoolSize(int newSize) {
251 maxPoolSize = newSize;
252 }
253
254
255
256
257
258
259 public boolean getCreateBuildersAtPoolLimit() {
260 return createBuildersAtPoolLimit;
261 }
262
263
264
265
266
267
268 public void setCreateBuildersAtPoolLimit(boolean createBuilders) {
269 createBuildersAtPoolLimit = createBuilders;
270 }
271
272
273
274
275
276
277 public Map<String, Object> getBuilderAttributes() {
278 return Collections.unmodifiableMap(builderAttributes);
279 }
280
281
282
283
284
285
286 public void setBuilderAttributes(Map<String, Object> newAttributes) {
287 builderAttributes = newAttributes;
288 dirtyBuilderConfiguration = true;
289 }
290
291
292
293
294
295
296 public boolean isCoalescing() {
297 return coalescing;
298 }
299
300
301
302
303
304
305 public void setCoalescing(boolean isCoalescing) {
306 coalescing = isCoalescing;
307 dirtyBuilderConfiguration = true;
308 }
309
310
311
312
313
314
315 public boolean isExpandEntityReferences() {
316 return expandEntityReferences;
317 }
318
319
320
321
322
323
324 public void setExpandEntityReferences(boolean expand) {
325 expandEntityReferences = expand;
326 dirtyBuilderConfiguration = true;
327 }
328
329
330
331
332
333
334 public Map<String, Boolean> getBuilderFeatures() {
335 return Collections.unmodifiableMap(builderFeatures);
336 }
337
338
339
340
341
342
343 public void setBuilderFeatures(Map<String, Boolean> newFeatures) {
344 builderFeatures = newFeatures;
345 dirtyBuilderConfiguration = true;
346 }
347
348
349
350
351
352
353 public boolean getIgnoreComments() {
354 return ignoreComments;
355 }
356
357
358
359
360
361
362 public void setIgnoreComments(boolean ignore) {
363 ignoreComments = ignore;
364 dirtyBuilderConfiguration = true;
365 }
366
367
368
369
370
371
372 public boolean isIgnoreElementContentWhitespace() {
373 return ignoreElementContentWhitespace;
374 }
375
376
377
378
379
380
381 public void setIgnoreElementContentWhitespace(boolean ignore) {
382 ignoreElementContentWhitespace = ignore;
383 dirtyBuilderConfiguration = true;
384 }
385
386
387
388
389
390
391 public boolean isNamespaceAware() {
392 return namespaceAware;
393 }
394
395
396
397
398
399
400 public void setNamespaceAware(boolean isNamespaceAware) {
401 namespaceAware = isNamespaceAware;
402 dirtyBuilderConfiguration = true;
403 }
404
405
406 public Schema getSchema() {
407 return schema;
408 }
409
410
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
424
425
426
427 public boolean isDTDValidating() {
428 return dtdValidating;
429 }
430
431
432
433
434
435
436 public void setDTDValidating(boolean isValidating) {
437 dtdValidating = isValidating;
438 dirtyBuilderConfiguration = true;
439 }
440
441
442
443
444
445
446 public boolean isXincludeAware() {
447 return xincludeAware;
448 }
449
450
451
452
453
454
455 public void setXincludeAware(boolean isXIncludeAware) {
456 xincludeAware = isXIncludeAware;
457 dirtyBuilderConfiguration = true;
458 }
459
460
461
462
463
464
465 protected long getPoolVersion() {
466 return poolVersion;
467 }
468
469
470
471
472
473
474 protected synchronized void initializePool() throws XMLParserException {
475 if (!dirtyBuilderConfiguration) {
476
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
515
516
517
518
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
541
542 protected class DocumentBuilderProxy extends DocumentBuilder {
543
544
545 private DocumentBuilder builder;
546
547
548 private ParserPool owningPool;
549
550
551 private long owningPoolVersion;
552
553
554
555
556
557
558
559 public DocumentBuilderProxy(DocumentBuilder target, BasicParserPool owner) {
560 owningPoolVersion = owner.getPoolVersion();
561 owningPool = owner;
562 builder = target;
563 }
564
565
566 public DOMImplementation getDOMImplementation() {
567 return builder.getDOMImplementation();
568 }
569
570
571 public Schema getSchema() {
572 return builder.getSchema();
573 }
574
575
576 public boolean isNamespaceAware() {
577 return builder.isNamespaceAware();
578 }
579
580
581 public boolean isValidating() {
582 return builder.isValidating();
583 }
584
585
586 public boolean isXIncludeAware() {
587 return builder.isXIncludeAware();
588 }
589
590
591 public Document newDocument() {
592 return builder.newDocument();
593 }
594
595
596 public Document parse(File f) throws SAXException, IOException {
597 return builder.parse(f);
598 }
599
600
601 public Document parse(InputSource is) throws SAXException, IOException {
602 return builder.parse(is);
603 }
604
605
606 public Document parse(InputStream is) throws SAXException, IOException {
607 return builder.parse(is);
608 }
609
610
611 public Document parse(InputStream is, String systemId) throws SAXException, IOException {
612 return builder.parse(is, systemId);
613 }
614
615
616 public Document parse(String uri) throws SAXException, IOException {
617 return builder.parse(uri);
618 }
619
620
621 public void reset() {
622
623 }
624
625
626 public void setEntityResolver(EntityResolver er) {
627 return;
628 }
629
630
631 public void setErrorHandler(ErrorHandler eh) {
632 return;
633 }
634
635
636
637
638
639
640 protected ParserPool getOwningPool() {
641 return owningPool;
642 }
643
644
645
646
647
648
649 protected long getPoolVersion() {
650 return owningPoolVersion;
651 }
652
653
654
655
656
657
658 protected DocumentBuilder getProxiedBuilder() {
659 return builder;
660 }
661
662
663 protected void finalize() throws Throwable {
664 super.finalize();
665 owningPool.returnBuilder(this);
666 }
667 }
668 }