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 4760 2013-04-08 17:56:26Z schulte $
029 *
030 */
031package org.jomc.model.modlet;
032
033import java.net.URL;
034import java.text.MessageFormat;
035import java.util.Enumeration;
036import java.util.Locale;
037import java.util.ResourceBundle;
038import java.util.logging.Level;
039import javax.xml.bind.JAXBElement;
040import javax.xml.bind.JAXBException;
041import javax.xml.bind.UnmarshalException;
042import javax.xml.bind.Unmarshaller;
043import org.jomc.model.Module;
044import org.jomc.model.Modules;
045import org.jomc.model.Text;
046import org.jomc.model.Texts;
047import org.jomc.modlet.Model;
048import org.jomc.modlet.ModelContext;
049import org.jomc.modlet.ModelException;
050import org.jomc.modlet.ModelProvider;
051
052/**
053 * Default object management and configuration {@code ModelProvider} implementation.
054 *
055 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
056 * @version $JOMC: DefaultModelProvider.java 4760 2013-04-08 17:56:26Z schulte $
057 * @see ModelContext#findModel(java.lang.String)
058 */
059public 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
587               ? t.getMessage() != null && t.getMessage().trim().length() > 0
588                 ? t.getMessage()
589                 : getMessage( t.getCause() )
590               : null;
591
592    }
593
594}