001    /*
002     *   Copyright (C) Christian Schulte, 2005-206
003     *   All rights reserved.
004     *
005     *   Redistribution and use in source and binary forms, with or without
006     *   modification, are permitted provided that the following conditions
007     *   are met:
008     *
009     *     o Redistributions of source code must retain the above copyright
010     *       notice, this list of conditions and the following disclaimer.
011     *
012     *     o Redistributions in binary form must reproduce the above copyright
013     *       notice, this list of conditions and the following disclaimer in
014     *       the documentation and/or other materials provided with the
015     *       distribution.
016     *
017     *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018     *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019     *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020     *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021     *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022     *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023     *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024     *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025     *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026     *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027     *
028     *   $JOMC: DefaultModelProvider.java 4201 2012-01-25 09:47:12Z schulte2005 $
029     *
030     */
031    package org.jomc.model.modlet;
032    
033    import java.net.URL;
034    import java.text.MessageFormat;
035    import java.util.Enumeration;
036    import java.util.Locale;
037    import java.util.ResourceBundle;
038    import java.util.logging.Level;
039    import javax.xml.bind.JAXBElement;
040    import javax.xml.bind.JAXBException;
041    import javax.xml.bind.UnmarshalException;
042    import javax.xml.bind.Unmarshaller;
043    import org.jomc.model.Module;
044    import org.jomc.model.Modules;
045    import org.jomc.model.Text;
046    import org.jomc.model.Texts;
047    import org.jomc.modlet.Model;
048    import org.jomc.modlet.ModelContext;
049    import org.jomc.modlet.ModelException;
050    import org.jomc.modlet.ModelProvider;
051    
052    /**
053     * Default object management and configuration {@code ModelProvider} implementation.
054     *
055     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
056     * @version $JOMC: DefaultModelProvider.java 4201 2012-01-25 09:47:12Z schulte2005 $
057     * @see ModelContext#findModel(java.lang.String)
058     */
059    public class DefaultModelProvider implements ModelProvider
060    {
061    
062        /**
063         * Constant for the name of the model context attribute backing property {@code enabled}.
064         * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
065         * @see ModelContext#getAttribute(java.lang.String)
066         * @since 1.2
067         */
068        public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProvider.enabledAttribute";
069    
070        /**
071         * Constant for the name of the system property controlling property {@code defaultEnabled}.
072         * @see #isDefaultEnabled()
073         */
074        private static final String DEFAULT_ENABLED_PROPERTY_NAME =
075            "org.jomc.model.modlet.DefaultModelProvider.defaultEnabled";
076    
077        /**
078         * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}.
079         * @see #isDefaultEnabled()
080         */
081        private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME =
082            "org.jomc.model.DefaultModelProvider.defaultEnabled";
083    
084        /**
085         * Default value of the flag indicating the provider is enabled by default.
086         * @see #isDefaultEnabled()
087         * @since 1.2
088         */
089        private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
090    
091        /** Flag indicating the provider is enabled by default. */
092        private static volatile Boolean defaultEnabled;
093    
094        /** Flag indicating the provider is enabled. */
095        private Boolean enabled;
096    
097        /**
098         * Constant for the name of the model context attribute backing property {@code moduleLocation}.
099         * @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                                                         Long.valueOf( 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 ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
587        }
588    
589    }