1 /*
2 * Copyright (C) Christian Schulte <cs@schulte.it>, 2005-206
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * o Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * o Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * $JOMC: DefaultModelProvider.java 5374 2016-09-05 12:24:12Z schulte $
29 *
30 */
31 package org.jomc.model.modlet;
32
33 import java.lang.reflect.UndeclaredThrowableException;
34 import java.net.URL;
35 import java.text.MessageFormat;
36 import java.util.Enumeration;
37 import java.util.LinkedList;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.ResourceBundle;
41 import java.util.concurrent.Callable;
42 import java.util.concurrent.CancellationException;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.Future;
45 import java.util.logging.Level;
46 import javax.xml.bind.JAXBElement;
47 import javax.xml.bind.JAXBException;
48 import javax.xml.bind.UnmarshalException;
49 import javax.xml.bind.Unmarshaller;
50 import javax.xml.validation.Schema;
51 import org.jomc.model.Module;
52 import org.jomc.model.Modules;
53 import org.jomc.model.Text;
54 import org.jomc.model.Texts;
55 import org.jomc.modlet.Model;
56 import org.jomc.modlet.ModelContext;
57 import org.jomc.modlet.ModelException;
58 import org.jomc.modlet.ModelProvider;
59
60 /**
61 * Default object management and configuration {@code ModelProvider} implementation.
62 *
63 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
64 * @version $JOMC: DefaultModelProvider.java 5374 2016-09-05 12:24:12Z schulte $
65 * @see ModelContext#findModel(java.lang.String)
66 */
67 public class DefaultModelProvider implements ModelProvider
68 {
69
70 /**
71 * Constant for the name of the model context attribute backing property {@code enabled}.
72 *
73 * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
74 * @see ModelContext#getAttribute(java.lang.String)
75 * @since 1.2
76 */
77 public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProvider.enabledAttribute";
78
79 /**
80 * Constant for the name of the system property controlling property {@code defaultEnabled}.
81 *
82 * @see #isDefaultEnabled()
83 */
84 private static final String DEFAULT_ENABLED_PROPERTY_NAME =
85 "org.jomc.model.modlet.DefaultModelProvider.defaultEnabled";
86
87 /**
88 * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}.
89 * @see #isDefaultEnabled()
90 */
91 private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME =
92 "org.jomc.model.DefaultModelProvider.defaultEnabled";
93
94 /**
95 * Default value of the flag indicating the provider is enabled by default.
96 *
97 * @see #isDefaultEnabled()
98 * @since 1.2
99 */
100 private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
101
102 /**
103 * Flag indicating the provider is enabled by default.
104 */
105 private static volatile Boolean defaultEnabled;
106
107 /**
108 * Flag indicating the provider is enabled.
109 */
110 private Boolean enabled;
111
112 /**
113 * Constant for the name of the model context attribute backing property {@code moduleLocation}.
114 *
115 * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
116 * @see ModelContext#getAttribute(java.lang.String)
117 * @since 1.2
118 */
119 public static final String MODULE_LOCATION_ATTRIBUTE_NAME =
120 "org.jomc.model.modlet.DefaultModelProvider.moduleLocationAttribute";
121
122 /**
123 * Constant for the name of the system property controlling property {@code defaultModuleLocation}.
124 *
125 * @see #getDefaultModuleLocation()
126 */
127 private static final String DEFAULT_MODULE_LOCATION_PROPERTY_NAME =
128 "org.jomc.model.modlet.DefaultModelProvider.defaultModuleLocation";
129
130 /**
131 * Constant for the name of the deprecated system property controlling property {@code defaultModuleLocation}.
132 * @see #getDefaultModuleLocation()
133 */
134 private static final String DEPRECATED_DEFAULT_MODULE_LOCATION_PROPERTY_NAME =
135 "org.jomc.model.DefaultModelProvider.defaultModuleLocation";
136
137 /**
138 * Class path location searched for modules by default.
139 *
140 * @see #getDefaultModuleLocation()
141 */
142 private static final String DEFAULT_MODULE_LOCATION = "META-INF/jomc.xml";
143
144 /**
145 * Default module location.
146 */
147 private static volatile String defaultModuleLocation;
148
149 /**
150 * Module location of the instance.
151 */
152 private String moduleLocation;
153
154 /**
155 * Constant for the name of the model context attribute backing property {@code validating}.
156 *
157 * @see #findModules(org.jomc.modlet.ModelContext, java.lang.String, java.lang.String)
158 * @see ModelContext#getAttribute(java.lang.String)
159 * @since 1.2
160 */
161 public static final String VALIDATING_ATTRIBUTE_NAME =
162 "org.jomc.model.modlet.DefaultModelProvider.validatingAttribute";
163
164 /**
165 * Constant for the name of the system property controlling property {@code defaultValidating}.
166 *
167 * @see #isDefaultValidating()
168 * @since 1.2
169 */
170 private static final String DEFAULT_VALIDATING_PROPERTY_NAME =
171 "org.jomc.model.modlet.DefaultModelProvider.defaultValidating";
172
173 /**
174 * Default value of the flag indicating the provider is validating resources by default.
175 *
176 * @see #isDefaultValidating()
177 * @since 1.2
178 */
179 private static final Boolean DEFAULT_VALIDATING = Boolean.TRUE;
180
181 /**
182 * Flag indicating the provider is validating resources by default.
183 *
184 * @since 1.2
185 */
186 private static volatile Boolean defaultValidating;
187
188 /**
189 * Flag indicating the provider is validating resources.
190 *
191 * @since 1.2
192 */
193 private Boolean validating;
194
195 /**
196 * Creates a new {@code DefaultModelProvider} instance.
197 */
198 public DefaultModelProvider()
199 {
200 super();
201 }
202
203 /**
204 * Gets a flag indicating the provider is enabled by default.
205 * <p>
206 * The default enabled flag is controlled by system property
207 * {@code org.jomc.model.modlet.DefaultModelProvider.defaultEnabled} holding a value indicating the provider is
208 * enabled by default. If that property is not set, the {@code true} default is returned.
209 * </p>
210 *
211 * @return {@code true}, if the provider is enabled by default; {@code false}, if the provider is disabled by
212 * default.
213 *
214 * @see #setDefaultEnabled(java.lang.Boolean)
215 */
216 public static boolean isDefaultEnabled()
217 {
218 if ( defaultEnabled == null )
219 {
220 defaultEnabled =
221 Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME,
222 System.getProperty( DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME,
223 Boolean.toString( DEFAULT_ENABLED ) ) ) );
224
225 }
226
227 return defaultEnabled;
228 }
229
230 /**
231 * Sets the flag indicating the provider is enabled by default.
232 *
233 * @param value The new value of the flag indicating the provider is enabled by default or {@code null}.
234 *
235 * @see #isDefaultEnabled()
236 */
237 public static void setDefaultEnabled( final Boolean value )
238 {
239 defaultEnabled = value;
240 }
241
242 /**
243 * Gets a flag indicating the provider is enabled.
244 *
245 * @return {@code true}, if the provider is enabled; {@code false}, if the provider is disabled.
246 *
247 * @see #isDefaultEnabled()
248 * @see #setEnabled(java.lang.Boolean)
249 */
250 public final boolean isEnabled()
251 {
252 if ( this.enabled == null )
253 {
254 this.enabled = isDefaultEnabled();
255 }
256
257 return this.enabled;
258 }
259
260 /**
261 * Sets the flag indicating the provider is enabled.
262 *
263 * @param value The new value of the flag indicating the provider is enabled or {@code null}.
264 *
265 * @see #isEnabled()
266 */
267 public final void setEnabled( final Boolean value )
268 {
269 this.enabled = value;
270 }
271
272 /**
273 * Gets the default location searched for module resources.
274 * <p>
275 * The default module location is controlled by system property
276 * {@code org.jomc.model.modlet.DefaultModelProvider.defaultModuleLocation} holding the location to search for
277 * module resources by default. If that property is not set, the {@code META-INF/jomc.xml} default is returned.
278 * </p>
279 *
280 * @return The location searched for module resources by default.
281 *
282 * @see #setDefaultModuleLocation(java.lang.String)
283 */
284 public static String getDefaultModuleLocation()
285 {
286 if ( defaultModuleLocation == null )
287 {
288 defaultModuleLocation =
289 System.getProperty( DEFAULT_MODULE_LOCATION_PROPERTY_NAME,
290 System.getProperty( DEPRECATED_DEFAULT_MODULE_LOCATION_PROPERTY_NAME,
291 DEFAULT_MODULE_LOCATION ) );
292
293 }
294
295 return defaultModuleLocation;
296 }
297
298 /**
299 * Sets the default location searched for module resources.
300 *
301 * @param value The new default location to search for module resources or {@code null}.
302 *
303 * @see #getDefaultModuleLocation()
304 */
305 public static void setDefaultModuleLocation( final String value )
306 {
307 defaultModuleLocation = value;
308 }
309
310 /**
311 * Gets the location searched for module resources.
312 *
313 * @return The location searched for module resources.
314 *
315 * @see #getDefaultModuleLocation()
316 * @see #setModuleLocation(java.lang.String)
317 */
318 public final String getModuleLocation()
319 {
320 if ( this.moduleLocation == null )
321 {
322 this.moduleLocation = getDefaultModuleLocation();
323 }
324
325 return this.moduleLocation;
326 }
327
328 /**
329 * Sets the location searched for module resources.
330 *
331 * @param value The new location to search for module resources or {@code null}.
332 *
333 * @see #getModuleLocation()
334 */
335 public final void setModuleLocation( final String value )
336 {
337 this.moduleLocation = value;
338 }
339
340 /**
341 * Gets a flag indicating the provider is validating resources by default.
342 * <p>
343 * The default validating flag is controlled by system property
344 * {@code org.jomc.model.modlet.DefaultModelProvider.defaultValidating} holding a value indicating the provider is
345 * validating resources by default. If that property is not set, the {@code true} default is returned.
346 * </p>
347 *
348 * @return {@code true}, if the provider is validating resources by default; {@code false}, if the provider is not
349 * validating resources by default.
350 *
351 * @see #isValidating()
352 * @see #setDefaultValidating(java.lang.Boolean)
353 *
354 * @since 1.2
355 */
356 public static boolean isDefaultValidating()
357 {
358 if ( defaultValidating == null )
359 {
360 defaultValidating = Boolean.valueOf( System.getProperty(
361 DEFAULT_VALIDATING_PROPERTY_NAME, Boolean.toString( DEFAULT_VALIDATING ) ) );
362
363 }
364
365 return defaultValidating;
366 }
367
368 /**
369 * Sets the flag indicating the provider is validating resources by default.
370 *
371 * @param value The new value of the flag indicating the provider is validating resources by default or
372 * {@code null}.
373 *
374 * @see #isDefaultValidating()
375 *
376 * @since 1.2
377 */
378 public static void setDefaultValidating( final Boolean value )
379 {
380 defaultValidating = value;
381 }
382
383 /**
384 * Gets a flag indicating the provider is validating resources.
385 *
386 * @return {@code true}, if the provider is validating resources; {@code false}, if the provider is not validating
387 * resources.
388 *
389 * @see #isDefaultValidating()
390 * @see #setValidating(java.lang.Boolean)
391 *
392 * @since 1.2
393 */
394 public final boolean isValidating()
395 {
396 if ( this.validating == null )
397 {
398 this.validating = isDefaultValidating();
399 }
400
401 return this.validating;
402 }
403
404 /**
405 * Sets the flag indicating the provider is validating resources.
406 *
407 * @param value The new value of the flag indicating the provider is validating resources or {@code null}.
408 *
409 * @see #isValidating()
410 *
411 * @since 1.2
412 */
413 public final void setValidating( final Boolean value )
414 {
415 this.validating = value;
416 }
417
418 /**
419 * Searches a given context for modules.
420 *
421 * @param context The context to search for modules.
422 * @param model The identifier of the model to search for modules.
423 * @param location The location to search at.
424 *
425 * @return The modules found at {@code location} in {@code context} or {@code null}, if no modules are found.
426 *
427 * @throws NullPointerException if {@code context}, {@code model} or {@code location} is {@code null}.
428 * @throws ModelException if searching the context fails.
429 *
430 * @see #isValidating()
431 * @see #VALIDATING_ATTRIBUTE_NAME
432 */
433 public Modules findModules( final ModelContext context, final String model, final String location )
434 throws ModelException
435 {
436 if ( context == null )
437 {
438 throw new NullPointerException( "context" );
439 }
440 if ( model == null )
441 {
442 throw new NullPointerException( "model" );
443 }
444 if ( location == null )
445 {
446 throw new NullPointerException( "location" );
447 }
448
449 try
450 {
451 boolean contextValidating = this.isValidating();
452 if ( DEFAULT_VALIDATING == contextValidating
453 && context.getAttribute( VALIDATING_ATTRIBUTE_NAME ) instanceof Boolean )
454 {
455 contextValidating = (Boolean) context.getAttribute( VALIDATING_ATTRIBUTE_NAME );
456 }
457
458 final long t0 = System.nanoTime();
459 final Text text = new Text();
460 text.setLanguage( "en" );
461 text.setValue( getMessage( "contextModulesInfo", location ) );
462
463 final Modules modules = new Modules();
464 modules.setDocumentation( new Texts() );
465 modules.getDocumentation().setDefaultLanguage( "en" );
466 modules.getDocumentation().getText().add( text );
467
468 final ThreadLocal<Unmarshaller> threadLocalUnmarshaller = new ThreadLocal<Unmarshaller>();
469 final Schema schema = contextValidating ? context.createSchema( model ) : null;
470
471 class UnmarshallTask implements Callable<Module>
472 {
473
474 private final URL resource;
475
476 UnmarshallTask( final URL resource )
477 {
478 super();
479 this.resource = resource;
480 }
481
482 public Module call() throws ModelException
483 {
484 try
485 {
486 Module module = null;
487
488 if ( context.isLoggable( Level.FINEST ) )
489 {
490 context.log( Level.FINEST, getMessage( "processing", this.resource.toExternalForm() ),
491 null );
492
493 }
494
495 Unmarshaller u = threadLocalUnmarshaller.get();
496 if ( u == null )
497 {
498 u = context.createUnmarshaller( model );
499 u.setSchema( schema );
500 threadLocalUnmarshaller.set( u );
501 }
502
503 Object content = u.unmarshal( this.resource );
504 if ( content instanceof JAXBElement<?> )
505 {
506 content = ( (JAXBElement<?>) content ).getValue();
507 }
508
509 if ( content instanceof Module )
510 {
511 final Module m = (Module) content;
512
513 if ( context.isLoggable( Level.FINEST ) )
514 {
515 context.log( Level.FINEST, getMessage(
516 "foundModule", m.getName(), m.getVersion() == null ? "" : m.getVersion() ),
517 null );
518
519 }
520
521 module = m;
522 }
523 else if ( context.isLoggable( Level.WARNING ) )
524 {
525 context.log( Level.WARNING, getMessage( "ignoringDocument",
526 content == null ? "<>" : content.toString(),
527 this.resource.toExternalForm() ), null );
528
529 }
530
531 return module;
532 }
533 catch ( final UnmarshalException e )
534 {
535 String message = getMessage( e );
536 if ( message == null && e.getLinkedException() != null )
537 {
538 message = getMessage( e.getLinkedException() );
539 }
540
541 message = getMessage( "unmarshalException", this.resource.toExternalForm(),
542 message != null ? " " + message : "" );
543
544 throw new ModelException( message, e );
545 }
546 catch ( final JAXBException e )
547 {
548 String message = getMessage( e );
549 if ( message == null && e.getLinkedException() != null )
550 {
551 message = getMessage( e.getLinkedException() );
552 }
553
554 throw new ModelException( message, e );
555 }
556 }
557
558 }
559
560 final List<UnmarshallTask> tasks = new LinkedList<UnmarshallTask>();
561 final Enumeration<URL> resources = context.findResources( location );
562
563 while ( resources.hasMoreElements() )
564 {
565 tasks.add( new UnmarshallTask( resources.nextElement() ) );
566 }
567
568 int count = 0;
569 if ( context.getExecutorService() != null && tasks.size() > 1 )
570 {
571 for ( final Future<Module> task : context.getExecutorService().invokeAll( tasks ) )
572 {
573 final Module m = task.get();
574
575 if ( m != null )
576 {
577 modules.getModule().add( m );
578 count++;
579 }
580 }
581 }
582 else
583 {
584 for ( final UnmarshallTask task : tasks )
585 {
586 final Module m = task.call();
587 if ( m != null )
588 {
589 modules.getModule().add( m );
590 count++;
591 }
592 }
593 }
594
595 if ( context.isLoggable( Level.FINE ) )
596 {
597 context.log( Level.FINE, getMessage( "contextReport", count, location, System.nanoTime() - t0 ), null );
598 }
599
600 return modules.getModule().isEmpty() ? null : modules;
601 }
602 catch ( final CancellationException e )
603 {
604 throw new ModelException( getMessage( e ), e );
605 }
606 catch ( final InterruptedException e )
607 {
608 throw new ModelException( getMessage( e ), e );
609 }
610 catch ( final ExecutionException e )
611 {
612 if ( e.getCause() instanceof ModelException )
613 {
614 throw (ModelException) e.getCause();
615 }
616 else if ( e.getCause() instanceof RuntimeException )
617 {
618 // The fork-join framework breaks the exception handling contract of Callable by re-throwing any
619 // exception caught using a runtime exception.
620 if ( e.getCause().getCause() instanceof ModelException )
621 {
622 throw (ModelException) e.getCause().getCause();
623 }
624 else if ( e.getCause().getCause() instanceof RuntimeException )
625 {
626 throw (RuntimeException) e.getCause().getCause();
627 }
628 else if ( e.getCause().getCause() instanceof Error )
629 {
630 throw (Error) e.getCause().getCause();
631 }
632 else if ( e.getCause().getCause() instanceof Exception )
633 {
634 // Checked exception not declared to be thrown by the Callable's 'call' method.
635 throw new UndeclaredThrowableException( e.getCause().getCause() );
636 }
637 else
638 {
639 throw (RuntimeException) e.getCause();
640 }
641 }
642 else if ( e.getCause() instanceof Error )
643 {
644 throw (Error) e.getCause();
645 }
646 else
647 {
648 // Checked exception not declared to be thrown by the Callable's 'call' method.
649 throw new UndeclaredThrowableException( e.getCause() );
650 }
651 }
652 }
653
654 /**
655 * {@inheritDoc}
656 *
657 * @return The {@code Model} found in the context or {@code null}, if no {@code Model} is found or the provider is
658 * disabled.
659 *
660 * @see #isEnabled()
661 * @see #getModuleLocation()
662 * @see #findModules(org.jomc.modlet.ModelContext, java.lang.String, java.lang.String)
663 * @see #ENABLED_ATTRIBUTE_NAME
664 * @see #MODULE_LOCATION_ATTRIBUTE_NAME
665 */
666 public Model findModel( final ModelContext context, final Model model ) throws ModelException
667 {
668 if ( context == null )
669 {
670 throw new NullPointerException( "context" );
671 }
672 if ( model == null )
673 {
674 throw new NullPointerException( "model" );
675 }
676
677 Model found = null;
678
679 boolean contextEnabled = this.isEnabled();
680 if ( DEFAULT_ENABLED == contextEnabled && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
681 {
682 contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
683 }
684
685 String contextModuleLocation = this.getModuleLocation();
686 if ( DEFAULT_MODULE_LOCATION.equals( contextModuleLocation )
687 && context.getAttribute( MODULE_LOCATION_ATTRIBUTE_NAME ) instanceof String )
688 {
689 contextModuleLocation = (String) context.getAttribute( MODULE_LOCATION_ATTRIBUTE_NAME );
690 }
691
692 if ( contextEnabled )
693 {
694 final Modules modules = this.findModules( context, model.getIdentifier(), contextModuleLocation );
695
696 if ( modules != null )
697 {
698 found = model.clone();
699 ModelHelper.addModules( found, modules );
700 }
701 }
702 else if ( context.isLoggable( Level.FINER ) )
703 {
704 context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(),
705 model.getIdentifier() ), null );
706
707 }
708
709 return found;
710 }
711
712 private static String getMessage( final String key, final Object... args )
713 {
714 return MessageFormat.format( ResourceBundle.getBundle(
715 DefaultModelProvider.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
716
717 }
718
719 private static String getMessage( final Throwable t )
720 {
721 return t != null
722 ? t.getMessage() != null && t.getMessage().trim().length() > 0
723 ? t.getMessage()
724 : getMessage( t.getCause() )
725 : null;
726
727 }
728
729 }