View Javadoc
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 }