View Javadoc
1   /*
2    *   Copyright (C) Christian Schulte, 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 4941 2014-03-25 03:48:34Z schulte $
29   *
30   */
31  package org.jomc.model.modlet;
32  
33  import java.net.URL;
34  import java.text.MessageFormat;
35  import java.util.Enumeration;
36  import java.util.Locale;
37  import java.util.ResourceBundle;
38  import java.util.logging.Level;
39  import javax.xml.bind.JAXBElement;
40  import javax.xml.bind.JAXBException;
41  import javax.xml.bind.UnmarshalException;
42  import javax.xml.bind.Unmarshaller;
43  import org.jomc.model.Module;
44  import org.jomc.model.Modules;
45  import org.jomc.model.Text;
46  import org.jomc.model.Texts;
47  import org.jomc.modlet.Model;
48  import org.jomc.modlet.ModelContext;
49  import org.jomc.modlet.ModelException;
50  import org.jomc.modlet.ModelProvider;
51  
52  /**
53   * Default object management and configuration {@code ModelProvider} implementation.
54   *
55   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
56   * @version $JOMC: DefaultModelProvider.java 4941 2014-03-25 03:48:34Z schulte $
57   * @see ModelContext#findModel(java.lang.String)
58   */
59  public class DefaultModelProvider implements ModelProvider
60  {
61  
62      /**
63       * Constant for the name of the model context attribute backing property {@code enabled}.
64       * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
65       * @see ModelContext#getAttribute(java.lang.String)
66       * @since 1.2
67       */
68      public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProvider.enabledAttribute";
69  
70      /**
71       * Constant for the name of the system property controlling property {@code defaultEnabled}.
72       * @see #isDefaultEnabled()
73       */
74      private static final String DEFAULT_ENABLED_PROPERTY_NAME =
75          "org.jomc.model.modlet.DefaultModelProvider.defaultEnabled";
76  
77      /**
78       * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}.
79       * @see #isDefaultEnabled()
80       */
81      private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME =
82          "org.jomc.model.DefaultModelProvider.defaultEnabled";
83  
84      /**
85       * Default value of the flag indicating the provider is enabled by default.
86       * @see #isDefaultEnabled()
87       * @since 1.2
88       */
89      private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
90  
91      /** Flag indicating the provider is enabled by default. */
92      private static volatile Boolean defaultEnabled;
93  
94      /** Flag indicating the provider is enabled. */
95      private Boolean enabled;
96  
97      /**
98       * Constant for the name of the model context attribute backing property {@code moduleLocation}.
99       * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
100      * @see ModelContext#getAttribute(java.lang.String)
101      * @since 1.2
102      */
103     public static final String MODULE_LOCATION_ATTRIBUTE_NAME =
104         "org.jomc.model.modlet.DefaultModelProvider.moduleLocationAttribute";
105 
106     /**
107      * Constant for the name of the system property controlling property {@code defaultModuleLocation}.
108      * @see #getDefaultModuleLocation()
109      */
110     private static final String DEFAULT_MODULE_LOCATION_PROPERTY_NAME =
111         "org.jomc.model.modlet.DefaultModelProvider.defaultModuleLocation";
112 
113     /**
114      * Constant for the name of the deprecated system property controlling property {@code defaultModuleLocation}.
115      * @see #getDefaultModuleLocation()
116      */
117     private static final String DEPRECATED_DEFAULT_MODULE_LOCATION_PROPERTY_NAME =
118         "org.jomc.model.DefaultModelProvider.defaultModuleLocation";
119 
120     /**
121      * Class path location searched for modules by default.
122      * @see #getDefaultModuleLocation()
123      */
124     private static final String DEFAULT_MODULE_LOCATION = "META-INF/jomc.xml";
125 
126     /** Default module location. */
127     private static volatile String defaultModuleLocation;
128 
129     /** Module location of the instance. */
130     private String moduleLocation;
131 
132     /**
133      * Constant for the name of the model context attribute backing property {@code validating}.
134      * @see #findModules(org.jomc.modlet.ModelContext, java.lang.String, java.lang.String)
135      * @see ModelContext#getAttribute(java.lang.String)
136      * @since 1.2
137      */
138     public static final String VALIDATING_ATTRIBUTE_NAME =
139         "org.jomc.model.modlet.DefaultModelProvider.validatingAttribute";
140 
141     /**
142      * Constant for the name of the system property controlling property {@code defaultValidating}.
143      * @see #isDefaultValidating()
144      * @since 1.2
145      */
146     private static final String DEFAULT_VALIDATING_PROPERTY_NAME =
147         "org.jomc.model.modlet.DefaultModelProvider.defaultValidating";
148 
149     /**
150      * Default value of the flag indicating the provider is validating resources by default.
151      * @see #isDefaultValidating()
152      * @since 1.2
153      */
154     private static final Boolean DEFAULT_VALIDATING = Boolean.TRUE;
155 
156     /**
157      * Flag indicating the provider is validating resources by default.
158      * @since 1.2
159      */
160     private static volatile Boolean defaultValidating;
161 
162     /**
163      * Flag indicating the provider is validating resources.
164      * @since 1.2
165      */
166     private Boolean validating;
167 
168     /** Creates a new {@code DefaultModelProvider} instance. */
169     public DefaultModelProvider()
170     {
171         super();
172     }
173 
174     /**
175      * Gets a flag indicating the provider is enabled by default.
176      * <p>The default enabled flag is controlled by system property
177      * {@code org.jomc.model.modlet.DefaultModelProvider.defaultEnabled} holding a value indicating the provider is
178      * enabled by default. If that property is not set, the {@code true} default is returned.</p>
179      *
180      * @return {@code true}, if the provider is enabled by default; {@code false}, if the provider is disabled by
181      * default.
182      *
183      * @see #setDefaultEnabled(java.lang.Boolean)
184      */
185     public static boolean isDefaultEnabled()
186     {
187         if ( defaultEnabled == null )
188         {
189             defaultEnabled =
190                 Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME,
191                                                      System.getProperty( DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME,
192                                                                          Boolean.toString( DEFAULT_ENABLED ) ) ) );
193 
194         }
195 
196         return defaultEnabled;
197     }
198 
199     /**
200      * Sets the flag indicating the provider is enabled by default.
201      *
202      * @param value The new value of the flag indicating the provider is enabled by default or {@code null}.
203      *
204      * @see #isDefaultEnabled()
205      */
206     public static void setDefaultEnabled( final Boolean value )
207     {
208         defaultEnabled = value;
209     }
210 
211     /**
212      * Gets a flag indicating the provider is enabled.
213      *
214      * @return {@code true}, if the provider is enabled; {@code false}, if the provider is disabled.
215      *
216      * @see #isDefaultEnabled()
217      * @see #setEnabled(java.lang.Boolean)
218      */
219     public final boolean isEnabled()
220     {
221         if ( this.enabled == null )
222         {
223             this.enabled = isDefaultEnabled();
224         }
225 
226         return this.enabled;
227     }
228 
229     /**
230      * Sets the flag indicating the provider is enabled.
231      *
232      * @param value The new value of the flag indicating the provider is enabled or {@code null}.
233      *
234      * @see #isEnabled()
235      */
236     public final void setEnabled( final Boolean value )
237     {
238         this.enabled = value;
239     }
240 
241     /**
242      * Gets the default location searched for module resources.
243      * <p>The default module location is controlled by system property
244      * {@code org.jomc.model.modlet.DefaultModelProvider.defaultModuleLocation} holding the location to search for
245      * module resources by default. If that property is not set, the {@code META-INF/jomc.xml} default is returned.</p>
246      *
247      * @return The location searched for module resources by default.
248      *
249      * @see #setDefaultModuleLocation(java.lang.String)
250      */
251     public static String getDefaultModuleLocation()
252     {
253         if ( defaultModuleLocation == null )
254         {
255             defaultModuleLocation =
256                 System.getProperty( DEFAULT_MODULE_LOCATION_PROPERTY_NAME,
257                                     System.getProperty( DEPRECATED_DEFAULT_MODULE_LOCATION_PROPERTY_NAME,
258                                                         DEFAULT_MODULE_LOCATION ) );
259 
260         }
261 
262         return defaultModuleLocation;
263     }
264 
265     /**
266      * Sets the default location searched for module resources.
267      *
268      * @param value The new default location to search for module resources or {@code null}.
269      *
270      * @see #getDefaultModuleLocation()
271      */
272     public static void setDefaultModuleLocation( final String value )
273     {
274         defaultModuleLocation = value;
275     }
276 
277     /**
278      * Gets the location searched for module resources.
279      *
280      * @return The location searched for module resources.
281      *
282      * @see #getDefaultModuleLocation()
283      * @see #setModuleLocation(java.lang.String)
284      */
285     public final String getModuleLocation()
286     {
287         if ( this.moduleLocation == null )
288         {
289             this.moduleLocation = getDefaultModuleLocation();
290         }
291 
292         return this.moduleLocation;
293     }
294 
295     /**
296      * Sets the location searched for module resources.
297      *
298      * @param value The new location to search for module resources or {@code null}.
299      *
300      * @see #getModuleLocation()
301      */
302     public final void setModuleLocation( final String value )
303     {
304         this.moduleLocation = value;
305     }
306 
307     /**
308      * Gets a flag indicating the provider is validating resources by default.
309      * <p>The default validating flag is controlled by system property
310      * {@code org.jomc.model.modlet.DefaultModelProvider.defaultValidating} holding a value indicating the provider is
311      * validating resources by default. If that property is not set, the {@code true} default is returned.</p>
312      *
313      * @return {@code true}, if the provider is validating resources by default; {@code false}, if the provider is not
314      * validating resources by default.
315      *
316      * @see #isValidating()
317      * @see #setDefaultValidating(java.lang.Boolean)
318      *
319      * @since 1.2
320      */
321     public static boolean isDefaultValidating()
322     {
323         if ( defaultValidating == null )
324         {
325             defaultValidating = Boolean.valueOf( System.getProperty(
326                 DEFAULT_VALIDATING_PROPERTY_NAME, Boolean.toString( DEFAULT_VALIDATING ) ) );
327 
328         }
329 
330         return defaultValidating;
331     }
332 
333     /**
334      * Sets the flag indicating the provider is validating resources by default.
335      *
336      * @param value The new value of the flag indicating the provider is validating resources by default or
337      * {@code null}.
338      *
339      * @see #isDefaultValidating()
340      *
341      * @since 1.2
342      */
343     public static void setDefaultValidating( final Boolean value )
344     {
345         defaultValidating = value;
346     }
347 
348     /**
349      * Gets a flag indicating the provider is validating resources.
350      *
351      * @return {@code true}, if the provider is validating resources; {@code false}, if the provider is not validating
352      * resources.
353      *
354      * @see #isDefaultValidating()
355      * @see #setValidating(java.lang.Boolean)
356      *
357      * @since 1.2
358      */
359     public final boolean isValidating()
360     {
361         if ( this.validating == null )
362         {
363             this.validating = isDefaultValidating();
364         }
365 
366         return this.validating;
367     }
368 
369     /**
370      * Sets the flag indicating the provider is validating resources.
371      *
372      * @param value The new value of the flag indicating the provider is validating resources or {@code null}.
373      *
374      * @see #isValidating()
375      *
376      * @since 1.2
377      */
378     public final void setValidating( final Boolean value )
379     {
380         this.validating = value;
381     }
382 
383     /**
384      * Searches a given context for modules.
385      *
386      * @param context The context to search for modules.
387      * @param model The identifier of the model to search for modules.
388      * @param location The location to search at.
389      *
390      * @return The modules found at {@code location} in {@code context} or {@code null}, if no modules are found.
391      *
392      * @throws NullPointerException if {@code context}, {@code model} or {@code location} is {@code null}.
393      * @throws ModelException if searching the context fails.
394      *
395      * @see #isValidating()
396      * @see #VALIDATING_ATTRIBUTE_NAME
397      */
398     public Modules findModules( final ModelContext context, final String model, final String location )
399         throws ModelException
400     {
401         if ( context == null )
402         {
403             throw new NullPointerException( "context" );
404         }
405         if ( model == null )
406         {
407             throw new NullPointerException( "model" );
408         }
409         if ( location == null )
410         {
411             throw new NullPointerException( "location" );
412         }
413 
414         URL url = null;
415 
416         try
417         {
418             boolean contextValidating = this.isValidating();
419             if ( DEFAULT_VALIDATING == contextValidating
420                  && context.getAttribute( VALIDATING_ATTRIBUTE_NAME ) instanceof Boolean )
421             {
422                 contextValidating = (Boolean) context.getAttribute( VALIDATING_ATTRIBUTE_NAME );
423             }
424 
425             final long t0 = System.currentTimeMillis();
426             final Text text = new Text();
427             text.setLanguage( "en" );
428             text.setValue( getMessage( "contextModulesInfo", location ) );
429 
430             final Modules modules = new Modules();
431             modules.setDocumentation( new Texts() );
432             modules.getDocumentation().setDefaultLanguage( "en" );
433             modules.getDocumentation().getText().add( text );
434 
435             final Unmarshaller u = context.createUnmarshaller( model );
436             final Enumeration<URL> resources = context.findResources( location );
437 
438             if ( contextValidating )
439             {
440                 u.setSchema( context.createSchema( model ) );
441             }
442 
443             int count = 0;
444             while ( resources.hasMoreElements() )
445             {
446                 count++;
447                 url = resources.nextElement();
448 
449                 if ( context.isLoggable( Level.FINEST ) )
450                 {
451                     context.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null );
452                 }
453 
454                 Object content = u.unmarshal( url );
455                 if ( content instanceof JAXBElement<?> )
456                 {
457                     content = ( (JAXBElement<?>) content ).getValue();
458                 }
459 
460                 if ( content instanceof Module )
461                 {
462                     final Module m = (Module) content;
463                     if ( context.isLoggable( Level.FINEST ) )
464                     {
465                         context.log( Level.FINEST, getMessage(
466                             "foundModule", m.getName(), m.getVersion() == null ? "" : m.getVersion() ), null );
467 
468                     }
469 
470                     modules.getModule().add( m );
471                 }
472                 else if ( context.isLoggable( Level.WARNING ) )
473                 {
474                     context.log( Level.WARNING, getMessage( "ignoringDocument",
475                                                             content == null ? "<>" : content.toString(),
476                                                             url.toExternalForm() ), null );
477 
478                 }
479             }
480 
481             if ( context.isLoggable( Level.FINE ) )
482             {
483                 context.log( Level.FINE, getMessage( "contextReport", count, location,
484                                                      System.currentTimeMillis() - t0 ), null );
485 
486             }
487 
488             return modules.getModule().isEmpty() ? null : modules;
489         }
490         catch ( final UnmarshalException e )
491         {
492             String message = getMessage( e );
493             if ( message == null && e.getLinkedException() != null )
494             {
495                 message = getMessage( e.getLinkedException() );
496             }
497 
498             if ( url != null )
499             {
500                 message = getMessage( "unmarshalException", url.toExternalForm(),
501                                       message != null ? " " + message : "" );
502 
503             }
504 
505             throw new ModelException( message, e );
506         }
507         catch ( final JAXBException e )
508         {
509             String message = getMessage( e );
510             if ( message == null && e.getLinkedException() != null )
511             {
512                 message = getMessage( e.getLinkedException() );
513             }
514 
515             throw new ModelException( message, e );
516         }
517     }
518 
519     /**
520      * {@inheritDoc}
521      *
522      * @return The {@code Model} found in the context or {@code null}, if no {@code Model} is found or the provider is
523      * disabled.
524      *
525      * @see #isEnabled()
526      * @see #getModuleLocation()
527      * @see #findModules(org.jomc.modlet.ModelContext, java.lang.String, java.lang.String)
528      * @see #ENABLED_ATTRIBUTE_NAME
529      * @see #MODULE_LOCATION_ATTRIBUTE_NAME
530      */
531     public Model findModel( final ModelContext context, final Model model ) throws ModelException
532     {
533         if ( context == null )
534         {
535             throw new NullPointerException( "context" );
536         }
537         if ( model == null )
538         {
539             throw new NullPointerException( "model" );
540         }
541 
542         Model found = null;
543 
544         boolean contextEnabled = this.isEnabled();
545         if ( DEFAULT_ENABLED == contextEnabled && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
546         {
547             contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
548         }
549 
550         String contextModuleLocation = this.getModuleLocation();
551         if ( DEFAULT_MODULE_LOCATION.equals( contextModuleLocation )
552              && context.getAttribute( MODULE_LOCATION_ATTRIBUTE_NAME ) instanceof String )
553         {
554             contextModuleLocation = (String) context.getAttribute( MODULE_LOCATION_ATTRIBUTE_NAME );
555         }
556 
557         if ( contextEnabled )
558         {
559             final Modules modules = this.findModules( context, model.getIdentifier(), contextModuleLocation );
560 
561             if ( modules != null )
562             {
563                 found = model.clone();
564                 ModelHelper.addModules( found, modules );
565             }
566         }
567         else if ( context.isLoggable( Level.FINER ) )
568         {
569             context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(),
570                                                   model.getIdentifier() ), null );
571 
572         }
573 
574         return found;
575     }
576 
577     private static String getMessage( final String key, final Object... args )
578     {
579         return MessageFormat.format( ResourceBundle.getBundle(
580             DefaultModelProvider.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
581 
582     }
583 
584     private static String getMessage( final Throwable t )
585     {
586         return t != null
587                ? t.getMessage() != null && t.getMessage().trim().length() > 0
588                  ? t.getMessage()
589                  : getMessage( t.getCause() )
590                : null;
591 
592     }
593 
594 }