1 | /* |
2 | * Copyright (c) 2009 The JOMC Project |
3 | * Copyright (c) 2005 Christian Schulte <cs@jomc.org> |
4 | * All rights reserved. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * |
10 | * o Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * |
13 | * o Redistributions in binary form must reproduce the above copyright |
14 | * notice, this list of conditions and the following disclaimer in |
15 | * the documentation and/or other materials provided with the |
16 | * distribution. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS" |
19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
20 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
21 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR |
22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
25 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
26 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
27 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
28 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | * |
30 | * $Id: DefaultModelContext.java 1393 2010-01-27 18:34:30Z schulte2005 $ |
31 | * |
32 | */ |
33 | package org.jomc.model; |
34 | |
35 | import java.io.BufferedReader; |
36 | import java.io.IOException; |
37 | import java.io.InputStream; |
38 | import java.io.Reader; |
39 | import java.io.StringReader; |
40 | import java.io.StringWriter; |
41 | import java.lang.ref.Reference; |
42 | import java.lang.ref.SoftReference; |
43 | import java.net.URI; |
44 | import java.net.URISyntaxException; |
45 | import java.net.URL; |
46 | import java.text.MessageFormat; |
47 | import java.util.ArrayList; |
48 | import java.util.Enumeration; |
49 | import java.util.HashSet; |
50 | import java.util.Iterator; |
51 | import java.util.List; |
52 | import java.util.Locale; |
53 | import java.util.Map; |
54 | import java.util.ResourceBundle; |
55 | import java.util.Set; |
56 | import java.util.jar.Attributes; |
57 | import java.util.jar.Manifest; |
58 | import java.util.logging.Level; |
59 | import javax.xml.XMLConstants; |
60 | import javax.xml.bind.JAXBContext; |
61 | import javax.xml.bind.JAXBException; |
62 | import javax.xml.bind.Marshaller; |
63 | import javax.xml.bind.Unmarshaller; |
64 | import javax.xml.transform.Source; |
65 | import javax.xml.transform.sax.SAXSource; |
66 | import javax.xml.validation.Schema; |
67 | import javax.xml.validation.SchemaFactory; |
68 | import javax.xml.validation.Validator; |
69 | import org.jomc.model.bootstrap.BootstrapContext; |
70 | import org.jomc.model.bootstrap.BootstrapException; |
71 | import org.jomc.model.bootstrap.Schemas; |
72 | import org.jomc.model.bootstrap.Service; |
73 | import org.jomc.model.bootstrap.Services; |
74 | import org.w3c.dom.ls.LSInput; |
75 | import org.w3c.dom.ls.LSResourceResolver; |
76 | import org.xml.sax.EntityResolver; |
77 | import org.xml.sax.InputSource; |
78 | import org.xml.sax.SAXException; |
79 | import org.xml.sax.SAXParseException; |
80 | import org.xml.sax.helpers.DefaultHandler; |
81 | |
82 | /** |
83 | * Default {@code ModelContext} implementation. |
84 | * |
85 | * @author <a href="mailto:cs@jomc.org">Christian Schulte</a> |
86 | * @version $Id: DefaultModelContext.java 1393 2010-01-27 18:34:30Z schulte2005 $ |
87 | * @see ModelContext#createModelContext(java.lang.ClassLoader) |
88 | */ |
89 | public class DefaultModelContext extends ModelContext |
90 | { |
91 | |
92 | /** Supported schema name extensions. */ |
93 | private static final String[] SCHEMA_EXTENSIONS = new String[] |
94 | { |
95 | "xsd" |
96 | }; |
97 | |
98 | /** Cached {@code Services}. */ |
99 | private Reference<Services> cachedServices = new SoftReference<Services>( null ); |
100 | |
101 | /** Cached {@code Schemas}. */ |
102 | private Reference<Schemas> cachedSchemas = new SoftReference<Schemas>( null ); |
103 | |
104 | /** Cached schema resources. */ |
105 | private Reference<Set<URI>> cachedSchemaResources = new SoftReference<Set<URI>>( null ); |
106 | |
107 | /** |
108 | * Creates a new {@code DefaultModelContext} instance taking a class loader. |
109 | * |
110 | * @param classLoader The class loader of the context. |
111 | */ |
112 | public DefaultModelContext( final ClassLoader classLoader ) |
113 | { |
114 | super( classLoader ); |
115 | } |
116 | |
117 | /** |
118 | * Searches the context for modules. |
119 | * |
120 | * @return The modules found in the context. |
121 | * |
122 | * @throws ModelException if searching modules fails. |
123 | * |
124 | * @see BootstrapContext#findServices() |
125 | * @see ModelProvider#findModules(org.jomc.model.ModelContext) |
126 | */ |
127 | @Override |
128 | public Modules findModules() throws ModelException |
129 | { |
130 | try |
131 | { |
132 | final Text text = new Text(); |
133 | text.setLanguage( "en" ); |
134 | text.setValue( getMessage( "contextModulesInfo" ) ); |
135 | |
136 | final Modules modules = new Modules(); |
137 | modules.setDocumentation( new Texts() ); |
138 | modules.getDocumentation().setDefaultLanguage( "en" ); |
139 | modules.getDocumentation().getText().add( text ); |
140 | |
141 | final List<Service> providers = this.getServices().getServices( ModelProvider.class ); |
142 | |
143 | if ( providers != null ) |
144 | { |
145 | for ( Service provider : providers ) |
146 | { |
147 | final Class<ModelProvider> modelProviderClass = |
148 | (Class<ModelProvider>) this.findClass( provider.getClazz() ); |
149 | |
150 | if ( modelProviderClass == null ) |
151 | { |
152 | throw new ModelException( getMessage( "serviceNotFound", provider.getOrdinal(), |
153 | provider.getIdentifier(), provider.getClazz() ) ); |
154 | |
155 | } |
156 | |
157 | final ModelProvider modelProvider = modelProviderClass.newInstance(); |
158 | final Modules provided = modelProvider.findModules( this ); |
159 | if ( provided != null ) |
160 | { |
161 | modules.getModule().addAll( provided.getModule() ); |
162 | } |
163 | } |
164 | } |
165 | |
166 | if ( this.isLoggable( Level.FINEST ) ) |
167 | { |
168 | final StringWriter stringWriter = new StringWriter(); |
169 | final Marshaller m = this.createMarshaller(); |
170 | m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE ); |
171 | m.marshal( new ObjectFactory().createModules( modules ), stringWriter ); |
172 | stringWriter.close(); |
173 | |
174 | this.log( Level.FINEST, getMessage( "foundModules" ), null ); |
175 | |
176 | final BufferedReader reader = new BufferedReader( new StringReader( stringWriter.toString() ) ); |
177 | String line; |
178 | |
179 | while ( ( line = reader.readLine() ) != null ) |
180 | { |
181 | this.log( Level.FINEST, "\t" + line, null ); |
182 | } |
183 | |
184 | reader.close(); |
185 | } |
186 | |
187 | return modules; |
188 | } |
189 | catch ( final BootstrapException e ) |
190 | { |
191 | throw new ModelException( e ); |
192 | } |
193 | catch ( final InstantiationException e ) |
194 | { |
195 | throw new ModelException( e ); |
196 | } |
197 | catch ( final IllegalAccessException e ) |
198 | { |
199 | throw new ModelException( e ); |
200 | } |
201 | catch ( final IOException e ) |
202 | { |
203 | throw new ModelException( e ); |
204 | } |
205 | catch ( final JAXBException e ) |
206 | { |
207 | throw new ModelException( e ); |
208 | } |
209 | } |
210 | |
211 | /** |
212 | * Processes modules. |
213 | * |
214 | * @param modules The modules to process. |
215 | * |
216 | * @return The processed modules. |
217 | * |
218 | * @throws NullPointerException if {@code modules} is {@code null}. |
219 | * @throws ModelException if processing modules fails. |
220 | * |
221 | * @see BootstrapContext#findServices() |
222 | * @see ModelProcessor#processModules(org.jomc.model.ModelContext, org.jomc.model.Modules) |
223 | */ |
224 | @Override |
225 | public Modules processModules( final Modules modules ) throws ModelException |
226 | { |
227 | if ( modules == null ) |
228 | { |
229 | throw new NullPointerException( "modules" ); |
230 | } |
231 | |
232 | try |
233 | { |
234 | Modules processed = modules; |
235 | final List<Service> processors = this.getServices().getServices( ModelProcessor.class ); |
236 | |
237 | if ( processors != null ) |
238 | { |
239 | for ( Service processor : processors ) |
240 | { |
241 | final Class<ModelProcessor> modelProcessorClass = |
242 | (Class<ModelProcessor>) this.findClass( processor.getClazz() ); |
243 | |
244 | if ( modelProcessorClass == null ) |
245 | { |
246 | throw new ModelException( getMessage( "serviceNotFound", processor.getOrdinal(), |
247 | processor.getIdentifier(), processor.getClazz() ) ); |
248 | |
249 | } |
250 | |
251 | final ModelProcessor modelProcessor = modelProcessorClass.newInstance(); |
252 | final Modules current = modelProcessor.processModules( this, processed ); |
253 | if ( current != null ) |
254 | { |
255 | processed = current; |
256 | } |
257 | } |
258 | } |
259 | |
260 | if ( this.isLoggable( Level.FINEST ) ) |
261 | { |
262 | final StringWriter stringWriter = new StringWriter(); |
263 | final Marshaller m = this.createMarshaller(); |
264 | m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE ); |
265 | m.marshal( new ObjectFactory().createModules( processed ), stringWriter ); |
266 | stringWriter.close(); |
267 | |
268 | this.log( Level.FINEST, getMessage( "processedModules" ), null ); |
269 | |
270 | final BufferedReader reader = new BufferedReader( new StringReader( stringWriter.toString() ) ); |
271 | String line; |
272 | |
273 | while ( ( line = reader.readLine() ) != null ) |
274 | { |
275 | this.log( Level.FINEST, "\t" + line, null ); |
276 | } |
277 | |
278 | reader.close(); |
279 | } |
280 | |
281 | return processed; |
282 | } |
283 | catch ( final BootstrapException e ) |
284 | { |
285 | throw new ModelException( e ); |
286 | } |
287 | catch ( final InstantiationException e ) |
288 | { |
289 | throw new ModelException( e ); |
290 | } |
291 | catch ( final IllegalAccessException e ) |
292 | { |
293 | throw new ModelException( e ); |
294 | } |
295 | catch ( final IOException e ) |
296 | { |
297 | throw new ModelException( e ); |
298 | } |
299 | catch ( final JAXBException e ) |
300 | { |
301 | throw new ModelException( e ); |
302 | } |
303 | } |
304 | |
305 | /** |
306 | * Validates a given model. |
307 | * |
308 | * @param model A source providing the model to validate. |
309 | * |
310 | * @return Validation report. |
311 | * |
312 | * @throws NullPointerException if {@code model} is {@code null}. |
313 | * @throws ModelException if validating the model fails. |
314 | */ |
315 | @Override |
316 | public ModelValidationReport validateModel( final Source model ) throws ModelException |
317 | { |
318 | if ( model == null ) |
319 | { |
320 | throw new NullPointerException( "model" ); |
321 | } |
322 | |
323 | final Schema schema = this.createSchema(); |
324 | final Validator validator = schema.newValidator(); |
325 | final ModelErrorHandler modelErrorHandler = new ModelErrorHandler( this ); |
326 | validator.setErrorHandler( modelErrorHandler ); |
327 | |
328 | try |
329 | { |
330 | validator.validate( model ); |
331 | } |
332 | catch ( final SAXException e ) |
333 | { |
334 | if ( this.isLoggable( Level.FINE ) ) |
335 | { |
336 | this.log( Level.FINE, e.getMessage(), e ); |
337 | } |
338 | |
339 | if ( modelErrorHandler.getReport().isModelValid() ) |
340 | { |
341 | throw new ModelException( e ); |
342 | } |
343 | } |
344 | catch ( final IOException e ) |
345 | { |
346 | throw new ModelException( e ); |
347 | } |
348 | |
349 | return modelErrorHandler.getReport(); |
350 | } |
351 | |
352 | /** |
353 | * Validates a given list of modules. |
354 | * |
355 | * @param modules The list of modules to validate. |
356 | * |
357 | * @return Validation report. |
358 | * |
359 | * @throws NullPointerException if {@code modules} is {@code null}. |
360 | * @throws ModelException if validating the modules fails. |
361 | * |
362 | * @see BootstrapContext#findServices() |
363 | * @see ModelValidator#validateModel(org.jomc.model.ModelContext, org.jomc.model.Modules) |
364 | */ |
365 | @Override |
366 | public ModelValidationReport validateModel( final Modules modules ) throws ModelException |
367 | { |
368 | if ( modules == null ) |
369 | { |
370 | throw new NullPointerException( "modules" ); |
371 | } |
372 | |
373 | try |
374 | { |
375 | final List<Service> validators = this.getServices().getServices( ModelValidator.class ); |
376 | final ModelValidationReport report = new ModelValidationReport(); |
377 | |
378 | if ( validators != null ) |
379 | { |
380 | for ( Service validator : validators ) |
381 | { |
382 | final Class<ModelValidator> modelValidatorClass = |
383 | (Class<ModelValidator>) this.findClass( validator.getClazz() ); |
384 | |
385 | if ( modelValidatorClass == null ) |
386 | { |
387 | throw new ModelException( getMessage( "serviceNotFound", validator.getOrdinal(), |
388 | validator.getIdentifier(), validator.getClazz() ) ); |
389 | |
390 | } |
391 | |
392 | final ModelValidator modelValidator = modelValidatorClass.newInstance(); |
393 | final ModelValidationReport current = modelValidator.validateModel( this, modules ); |
394 | if ( current != null ) |
395 | { |
396 | report.getDetails().addAll( current.getDetails() ); |
397 | } |
398 | } |
399 | } |
400 | |
401 | return report; |
402 | } |
403 | catch ( final BootstrapException e ) |
404 | { |
405 | throw new ModelException( e ); |
406 | } |
407 | catch ( final InstantiationException e ) |
408 | { |
409 | throw new ModelException( e ); |
410 | } |
411 | catch ( final IllegalAccessException e ) |
412 | { |
413 | throw new ModelException( e ); |
414 | } |
415 | } |
416 | |
417 | @Override |
418 | public EntityResolver createEntityResolver() throws ModelException |
419 | { |
420 | return new DefaultHandler() |
421 | { |
422 | |
423 | @Override |
424 | public InputSource resolveEntity( final String publicId, final String systemId ) |
425 | throws SAXException, IOException |
426 | { |
427 | if ( systemId == null ) |
428 | { |
429 | throw new NullPointerException( "systemId" ); |
430 | } |
431 | |
432 | InputSource schemaSource = null; |
433 | |
434 | try |
435 | { |
436 | org.jomc.model.bootstrap.Schema s = null; |
437 | final Schemas classpathSchemas = getSchemas(); |
438 | |
439 | if ( publicId != null ) |
440 | { |
441 | s = classpathSchemas.getSchemaByPublicId( publicId ); |
442 | } |
443 | if ( s == null ) |
444 | { |
445 | s = classpathSchemas.getSchemaBySystemId( systemId ); |
446 | } |
447 | |
448 | if ( s != null ) |
449 | { |
450 | schemaSource = new InputSource(); |
451 | schemaSource.setPublicId( s.getPublicId() != null ? s.getPublicId() : publicId ); |
452 | schemaSource.setSystemId( s.getSystemId() ); |
453 | |
454 | if ( s.getClasspathId() != null ) |
455 | { |
456 | final URL resource = findResource( s.getClasspathId() ); |
457 | |
458 | if ( resource != null ) |
459 | { |
460 | schemaSource.setSystemId( resource.toExternalForm() ); |
461 | } |
462 | else |
463 | { |
464 | if ( isLoggable( Level.WARNING ) ) |
465 | { |
466 | log( Level.WARNING, getMessage( "resourceNotFound", s.getClasspathId() ), null ); |
467 | } |
468 | } |
469 | } |
470 | |
471 | if ( isLoggable( Level.FINE ) ) |
472 | { |
473 | log( Level.FINE, getMessage( |
474 | "resolutionInfo", publicId + ", " + systemId, |
475 | schemaSource.getPublicId() + ", " + schemaSource.getSystemId() ), null ); |
476 | |
477 | } |
478 | } |
479 | |
480 | if ( schemaSource == null ) |
481 | { |
482 | final URI systemUri = new URI( systemId ); |
483 | String schemaName = systemUri.getPath(); |
484 | if ( schemaName != null ) |
485 | { |
486 | final int lastIndexOfSlash = schemaName.lastIndexOf( '/' ); |
487 | if ( lastIndexOfSlash != -1 && lastIndexOfSlash < schemaName.length() ) |
488 | { |
489 | schemaName = schemaName.substring( lastIndexOfSlash + 1 ); |
490 | } |
491 | |
492 | for ( URI uri : getSchemaResources() ) |
493 | { |
494 | if ( uri.getPath().endsWith( schemaName ) ) |
495 | { |
496 | schemaSource = new InputSource(); |
497 | schemaSource.setPublicId( publicId ); |
498 | schemaSource.setSystemId( uri.toASCIIString() ); |
499 | |
500 | if ( isLoggable( Level.FINE ) ) |
501 | { |
502 | log( Level.FINE, getMessage( "resolutionInfo", systemUri.toASCIIString(), |
503 | schemaSource.getSystemId() ), null ); |
504 | |
505 | } |
506 | |
507 | break; |
508 | } |
509 | } |
510 | } |
511 | else |
512 | { |
513 | if ( isLoggable( Level.WARNING ) ) |
514 | { |
515 | log( Level.WARNING, getMessage( "unsupportedSystemIdUri", systemId, |
516 | systemUri.toASCIIString() ), null ); |
517 | |
518 | } |
519 | |
520 | schemaSource = null; |
521 | } |
522 | } |
523 | } |
524 | catch ( final URISyntaxException e ) |
525 | { |
526 | if ( isLoggable( Level.WARNING ) ) |
527 | { |
528 | log( Level.WARNING, getMessage( "unsupportedSystemIdUri", systemId, e.getMessage() ), null ); |
529 | } |
530 | |
531 | schemaSource = null; |
532 | } |
533 | catch ( final BootstrapException e ) |
534 | { |
535 | throw (IOException) new IOException( getMessage( "failedResolvingSchemas" ) ).initCause( e ); |
536 | } |
537 | catch ( final ModelException e ) |
538 | { |
539 | throw (IOException) new IOException( getMessage( |
540 | "failedResolving", publicId, systemId, e.getMessage() ) ).initCause( e ); |
541 | |
542 | } |
543 | |
544 | return schemaSource; |
545 | } |
546 | |
547 | }; |
548 | } |
549 | |
550 | @Override |
551 | public LSResourceResolver createResourceResolver() throws ModelException |
552 | { |
553 | return new LSResourceResolver() |
554 | { |
555 | |
556 | public LSInput resolveResource( final String type, final String namespaceURI, final String publicId, |
557 | final String systemId, final String baseURI ) |
558 | { |
559 | final String resolvePublicId = namespaceURI == null ? publicId : namespaceURI; |
560 | final String resolveSystemId = systemId == null ? "" : systemId; |
561 | |
562 | try |
563 | { |
564 | if ( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals( type ) ) |
565 | { |
566 | final InputSource schemaSource = |
567 | createEntityResolver().resolveEntity( resolvePublicId, resolveSystemId ); |
568 | |
569 | if ( schemaSource != null ) |
570 | { |
571 | return new LSInput() |
572 | { |
573 | |
574 | public Reader getCharacterStream() |
575 | { |
576 | return schemaSource.getCharacterStream(); |
577 | } |
578 | |
579 | public void setCharacterStream( final Reader characterStream ) |
580 | { |
581 | log( Level.WARNING, getMessage( |
582 | "unsupportedOperation", "setCharacterStream", |
583 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
584 | |
585 | } |
586 | |
587 | public InputStream getByteStream() |
588 | { |
589 | return schemaSource.getByteStream(); |
590 | } |
591 | |
592 | public void setByteStream( final InputStream byteStream ) |
593 | { |
594 | log( Level.WARNING, getMessage( |
595 | "unsupportedOperation", "setByteStream", |
596 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
597 | |
598 | } |
599 | |
600 | public String getStringData() |
601 | { |
602 | return null; |
603 | } |
604 | |
605 | public void setStringData( final String stringData ) |
606 | { |
607 | log( Level.WARNING, getMessage( |
608 | "unsupportedOperation", "setStringData", |
609 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
610 | |
611 | } |
612 | |
613 | public String getSystemId() |
614 | { |
615 | return schemaSource.getSystemId(); |
616 | } |
617 | |
618 | public void setSystemId( final String systemId ) |
619 | { |
620 | log( Level.WARNING, getMessage( |
621 | "unsupportedOperation", "setSystemId", |
622 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
623 | |
624 | } |
625 | |
626 | public String getPublicId() |
627 | { |
628 | return schemaSource.getPublicId(); |
629 | } |
630 | |
631 | public void setPublicId( final String publicId ) |
632 | { |
633 | log( Level.WARNING, getMessage( |
634 | "unsupportedOperation", "setPublicId", |
635 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
636 | |
637 | } |
638 | |
639 | public String getBaseURI() |
640 | { |
641 | return baseURI; |
642 | } |
643 | |
644 | public void setBaseURI( final String baseURI ) |
645 | { |
646 | log( Level.WARNING, getMessage( |
647 | "unsupportedOperation", "setBaseURI", |
648 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
649 | |
650 | } |
651 | |
652 | public String getEncoding() |
653 | { |
654 | return schemaSource.getEncoding(); |
655 | } |
656 | |
657 | public void setEncoding( final String encoding ) |
658 | { |
659 | log( Level.WARNING, getMessage( |
660 | "unsupportedOperation", "setEncoding", |
661 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
662 | |
663 | } |
664 | |
665 | public boolean getCertifiedText() |
666 | { |
667 | return false; |
668 | } |
669 | |
670 | public void setCertifiedText( final boolean certifiedText ) |
671 | { |
672 | log( Level.WARNING, getMessage( |
673 | "unsupportedOperation", "setCertifiedText", |
674 | DefaultModelContext.class.getName() + ".LSResourceResolver" ), null ); |
675 | |
676 | } |
677 | |
678 | }; |
679 | } |
680 | |
681 | } |
682 | else if ( isLoggable( Level.WARNING ) ) |
683 | { |
684 | log( Level.WARNING, getMessage( "unsupportedResourceType", type ), null ); |
685 | } |
686 | } |
687 | catch ( final SAXException e ) |
688 | { |
689 | if ( isLoggable( Level.SEVERE ) ) |
690 | { |
691 | log( Level.SEVERE, getMessage( "failedResolving", resolvePublicId, resolveSystemId, |
692 | e.getMessage() ), e ); |
693 | |
694 | } |
695 | } |
696 | catch ( final IOException e ) |
697 | { |
698 | if ( isLoggable( Level.SEVERE ) ) |
699 | { |
700 | log( Level.SEVERE, getMessage( "failedResolving", resolvePublicId, resolveSystemId, |
701 | e.getMessage() ), e ); |
702 | |
703 | } |
704 | } |
705 | catch ( final ModelException e ) |
706 | { |
707 | if ( isLoggable( Level.SEVERE ) ) |
708 | { |
709 | log( Level.SEVERE, getMessage( "failedResolving", resolvePublicId, resolveSystemId, |
710 | e.getMessage() ), e ); |
711 | |
712 | } |
713 | } |
714 | |
715 | return null; |
716 | } |
717 | |
718 | }; |
719 | |
720 | } |
721 | |
722 | @Override |
723 | public Schema createSchema() throws ModelException |
724 | { |
725 | try |
726 | { |
727 | final SchemaFactory f = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ); |
728 | final Schemas schemas = this.getSchemas(); |
729 | final List<Source> sources = new ArrayList<Source>( schemas.getSchema().size() ); |
730 | final EntityResolver entityResolver = this.createEntityResolver(); |
731 | |
732 | for ( org.jomc.model.bootstrap.Schema s : schemas.getSchema() ) |
733 | { |
734 | final InputSource inputSource = entityResolver.resolveEntity( s.getPublicId(), s.getSystemId() ); |
735 | |
736 | if ( inputSource != null ) |
737 | { |
738 | sources.add( new SAXSource( inputSource ) ); |
739 | } |
740 | } |
741 | |
742 | if ( sources.isEmpty() ) |
743 | { |
744 | throw new ModelException( getMessage( "missingSchemas" ) ); |
745 | } |
746 | |
747 | f.setResourceResolver( this.createResourceResolver() ); |
748 | return f.newSchema( sources.toArray( new Source[ sources.size() ] ) ); |
749 | } |
750 | catch ( final BootstrapException e ) |
751 | { |
752 | throw new ModelException( e ); |
753 | } |
754 | catch ( final IOException e ) |
755 | { |
756 | throw new ModelException( e ); |
757 | } |
758 | catch ( final SAXException e ) |
759 | { |
760 | throw new ModelException( e ); |
761 | } |
762 | } |
763 | |
764 | @Override |
765 | public JAXBContext createContext() throws ModelException |
766 | { |
767 | try |
768 | { |
769 | final StringBuilder packageNames = new StringBuilder(); |
770 | |
771 | for ( final Iterator<org.jomc.model.bootstrap.Schema> s = this.getSchemas().getSchema().iterator(); |
772 | s.hasNext(); ) |
773 | { |
774 | final org.jomc.model.bootstrap.Schema schema = s.next(); |
775 | if ( schema.getContextId() != null ) |
776 | { |
777 | packageNames.append( ':' ).append( schema.getContextId() ); |
778 | if ( this.isLoggable( Level.CONFIG ) ) |
779 | { |
780 | this.log( Level.CONFIG, getMessage( "foundContext", schema.getContextId() ), null ); |
781 | } |
782 | } |
783 | } |
784 | |
785 | if ( packageNames.length() == 0 ) |
786 | { |
787 | throw new ModelException( getMessage( "missingSchemas" ) ); |
788 | } |
789 | |
790 | return JAXBContext.newInstance( packageNames.toString().substring( 1 ), this.getClassLoader() ); |
791 | } |
792 | catch ( final BootstrapException e ) |
793 | { |
794 | throw new ModelException( e ); |
795 | } |
796 | catch ( final JAXBException e ) |
797 | { |
798 | throw new ModelException( e ); |
799 | } |
800 | } |
801 | |
802 | @Override |
803 | public Marshaller createMarshaller() throws ModelException |
804 | { |
805 | try |
806 | { |
807 | final StringBuilder packageNames = new StringBuilder(); |
808 | final StringBuilder schemaLocation = new StringBuilder(); |
809 | |
810 | for ( final Iterator<org.jomc.model.bootstrap.Schema> s = this.getSchemas().getSchema().iterator(); |
811 | s.hasNext(); ) |
812 | { |
813 | final org.jomc.model.bootstrap.Schema schema = s.next(); |
814 | if ( schema.getContextId() != null ) |
815 | { |
816 | packageNames.append( ':' ).append( schema.getContextId() ); |
817 | } |
818 | if ( schema.getPublicId() != null && schema.getSystemId() != null ) |
819 | { |
820 | schemaLocation.append( ' ' ).append( schema.getPublicId() ).append( ' ' ). |
821 | append( schema.getSystemId() ); |
822 | |
823 | } |
824 | } |
825 | |
826 | if ( packageNames.length() == 0 ) |
827 | { |
828 | throw new ModelException( getMessage( "missingSchemas" ) ); |
829 | } |
830 | |
831 | final Marshaller m = |
832 | JAXBContext.newInstance( packageNames.toString().substring( 1 ), this.getClassLoader() ). |
833 | createMarshaller(); |
834 | |
835 | if ( schemaLocation.length() != 0 ) |
836 | { |
837 | m.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation.toString().substring( 1 ) ); |
838 | } |
839 | |
840 | return m; |
841 | } |
842 | catch ( final BootstrapException e ) |
843 | { |
844 | throw new ModelException( e ); |
845 | } |
846 | catch ( final JAXBException e ) |
847 | { |
848 | throw new ModelException( e ); |
849 | } |
850 | } |
851 | |
852 | @Override |
853 | public Unmarshaller createUnmarshaller() throws ModelException |
854 | { |
855 | try |
856 | { |
857 | return this.createContext().createUnmarshaller(); |
858 | } |
859 | catch ( final JAXBException e ) |
860 | { |
861 | throw new ModelException( e ); |
862 | } |
863 | } |
864 | |
865 | /** |
866 | * Gets the services of the instance. |
867 | * |
868 | * @return The services of the instance. |
869 | * |
870 | * @throws BootstrapException if getting the services fails. |
871 | */ |
872 | private Services getServices() throws BootstrapException |
873 | { |
874 | Services services = this.cachedServices.get(); |
875 | |
876 | if ( services == null ) |
877 | { |
878 | services = BootstrapContext.createBootstrapContext( this.getClassLoader() ).findServices(); |
879 | |
880 | if ( services != null && this.isLoggable( Level.CONFIG ) ) |
881 | { |
882 | for ( org.jomc.model.bootstrap.Service s : services.getService() ) |
883 | { |
884 | this.log( Level.CONFIG, getMessage( |
885 | "foundService", s.getOrdinal(), s.getIdentifier(), s.getClazz() ), null ); |
886 | |
887 | } |
888 | } |
889 | |
890 | this.cachedServices = new SoftReference( services ); |
891 | } |
892 | |
893 | return services; |
894 | } |
895 | |
896 | /** |
897 | * Gets the schemas of the instance. |
898 | * |
899 | * @return The schemas of the instance. |
900 | * |
901 | * @throws BootstrapException if getting the schemas fails. |
902 | */ |
903 | private Schemas getSchemas() throws BootstrapException |
904 | { |
905 | Schemas schemas = this.cachedSchemas.get(); |
906 | |
907 | if ( schemas == null ) |
908 | { |
909 | schemas = BootstrapContext.createBootstrapContext( this.getClassLoader() ).findSchemas(); |
910 | |
911 | if ( schemas != null && this.isLoggable( Level.CONFIG ) ) |
912 | { |
913 | for ( org.jomc.model.bootstrap.Schema s : schemas.getSchema() ) |
914 | { |
915 | this.log( Level.CONFIG, getMessage( "foundSchema", s.getPublicId(), s.getSystemId(), |
916 | s.getContextId(), s.getClasspathId() ), null ); |
917 | |
918 | } |
919 | } |
920 | |
921 | this.cachedSchemas = new SoftReference( schemas ); |
922 | } |
923 | |
924 | return schemas; |
925 | } |
926 | |
927 | /** |
928 | * Searches the context for {@code META-INF/MANIFEST.MF} resources and returns a set of URIs of entries whose name |
929 | * end with a known schema extension. |
930 | * |
931 | * @return Set of URIs of any matching entries. |
932 | * |
933 | * @throws IOException if reading fails. |
934 | * @throws URISyntaxException if parsing fails. |
935 | */ |
936 | private Set<URI> getSchemaResources() throws IOException, URISyntaxException |
937 | { |
938 | Set<URI> resources = this.cachedSchemaResources.get(); |
939 | |
940 | if ( resources == null ) |
941 | { |
942 | resources = new HashSet<URI>(); |
943 | final long t0 = System.currentTimeMillis(); |
944 | int count = 0; |
945 | |
946 | for ( final Enumeration<URL> e = this.getClassLoader().getResources( "META-INF/MANIFEST.MF" ); |
947 | e.hasMoreElements(); ) |
948 | { |
949 | count++; |
950 | final URL manifestUrl = e.nextElement(); |
951 | final String externalForm = manifestUrl.toExternalForm(); |
952 | final String baseUrl = externalForm.substring( 0, externalForm.indexOf( "META-INF" ) ); |
953 | final InputStream manifestStream = manifestUrl.openStream(); |
954 | final Manifest mf = new Manifest( manifestStream ); |
955 | manifestStream.close(); |
956 | |
957 | if ( this.isLoggable( Level.FINE ) ) |
958 | { |
959 | this.log( Level.FINE, getMessage( "processing", externalForm ), null ); |
960 | } |
961 | |
962 | for ( Map.Entry<String, Attributes> entry : mf.getEntries().entrySet() ) |
963 | { |
964 | for ( int i = SCHEMA_EXTENSIONS.length - 1; i >= 0; i-- ) |
965 | { |
966 | if ( entry.getKey().toLowerCase().endsWith( '.' + SCHEMA_EXTENSIONS[i].toLowerCase() ) ) |
967 | { |
968 | final URL schemaUrl = new URL( baseUrl + entry.getKey() ); |
969 | resources.add( schemaUrl.toURI() ); |
970 | |
971 | if ( this.isLoggable( Level.FINE ) ) |
972 | { |
973 | this.log( Level.FINE, getMessage( "foundSchemaCandidate", schemaUrl.toExternalForm() ), |
974 | null ); |
975 | |
976 | } |
977 | } |
978 | } |
979 | } |
980 | } |
981 | |
982 | if ( this.isLoggable( Level.FINE ) ) |
983 | { |
984 | this.log( Level.FINE, getMessage( "contextReport", count, "META-INF/MANIFEST.MF", |
985 | Long.valueOf( System.currentTimeMillis() - t0 ) ), null ); |
986 | |
987 | } |
988 | |
989 | this.cachedSchemaResources = new SoftReference( resources ); |
990 | } |
991 | |
992 | return resources; |
993 | } |
994 | |
995 | private static String getMessage( final String key, final Object... args ) |
996 | { |
997 | return MessageFormat.format( ResourceBundle.getBundle( |
998 | DefaultModelContext.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args ); |
999 | |
1000 | } |
1001 | |
1002 | } |
1003 | |
1004 | /** |
1005 | * {@code ErrorHandler} collecting {@code ModelValidationReport} details. |
1006 | * |
1007 | * @author <a href="mailto:cs@jomc.org">Christian Schulte</a> |
1008 | * @version $Id: DefaultModelContext.java 1393 2010-01-27 18:34:30Z schulte2005 $ |
1009 | */ |
1010 | class ModelErrorHandler extends DefaultHandler |
1011 | { |
1012 | |
1013 | /** The context of the instance. */ |
1014 | private ModelContext context; |
1015 | |
1016 | /** The report of the instance. */ |
1017 | private ModelValidationReport report; |
1018 | |
1019 | /** |
1020 | * Creates a new {@code ModelErrorHandler} instance taking a context. |
1021 | * |
1022 | * @param context The context of the instance. |
1023 | */ |
1024 | public ModelErrorHandler( final ModelContext context ) |
1025 | { |
1026 | this( context, null ); |
1027 | } |
1028 | |
1029 | /** |
1030 | * Creates a new {@code ModelErrorHandler} instance taking a report to use for collecting validation events. |
1031 | * |
1032 | * @param context The context of the instance. |
1033 | * @param report A report to use for collecting validation events. |
1034 | */ |
1035 | public ModelErrorHandler( final ModelContext context, final ModelValidationReport report ) |
1036 | { |
1037 | super(); |
1038 | this.context = context; |
1039 | this.report = report; |
1040 | } |
1041 | |
1042 | /** |
1043 | * Gets the report of the instance. |
1044 | * |
1045 | * @return The report of the instance. |
1046 | */ |
1047 | public ModelValidationReport getReport() |
1048 | { |
1049 | if ( this.report == null ) |
1050 | { |
1051 | this.report = new ModelValidationReport(); |
1052 | } |
1053 | |
1054 | return this.report; |
1055 | } |
1056 | |
1057 | @Override |
1058 | public void warning( final SAXParseException exception ) throws SAXException |
1059 | { |
1060 | if ( this.context != null && this.context.isLoggable( Level.FINE ) ) |
1061 | { |
1062 | this.context.log( Level.FINE, exception.getMessage(), exception ); |
1063 | } |
1064 | |
1065 | this.getReport().getDetails().add( new ModelValidationReport.Detail( |
1066 | "W3C XML 1.0 Recommendation - Warning condition", Level.WARNING, exception.getMessage(), null ) ); |
1067 | |
1068 | } |
1069 | |
1070 | @Override |
1071 | public void error( final SAXParseException exception ) throws SAXException |
1072 | { |
1073 | if ( this.context != null && this.context.isLoggable( Level.FINE ) ) |
1074 | { |
1075 | this.context.log( Level.FINE, exception.getMessage(), exception ); |
1076 | } |
1077 | |
1078 | this.getReport().getDetails().add( new ModelValidationReport.Detail( |
1079 | "W3C XML 1.0 Recommendation - Section 1.2 - Error", Level.SEVERE, exception.getMessage(), null ) ); |
1080 | |
1081 | } |
1082 | |
1083 | @Override |
1084 | public void fatalError( final SAXParseException exception ) throws SAXException |
1085 | { |
1086 | if ( this.context != null && this.context.isLoggable( Level.FINE ) ) |
1087 | { |
1088 | this.context.log( Level.FINE, exception.getMessage(), exception ); |
1089 | } |
1090 | |
1091 | this.getReport().getDetails().add( new ModelValidationReport.Detail( |
1092 | "W3C XML 1.0 Recommendation - Section 1.2 - Fatal Error", Level.SEVERE, exception.getMessage(), null ) ); |
1093 | |
1094 | } |
1095 | |
1096 | } |