001/*
002 *   Copyright (C) Christian Schulte <cs@schulte.it>, 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: DefaultModletProvider.java 5043 2015-05-27 07:03:39Z schulte $
029 *
030 */
031package org.jomc.modlet;
032
033import java.net.URL;
034import java.text.MessageFormat;
035import java.util.Enumeration;
036import java.util.ResourceBundle;
037import java.util.logging.Level;
038import javax.xml.bind.JAXBContext;
039import javax.xml.bind.JAXBElement;
040import javax.xml.bind.JAXBException;
041import javax.xml.bind.UnmarshalException;
042import javax.xml.bind.Unmarshaller;
043
044/**
045 * Default {@code ModletProvider} implementation.
046 *
047 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
048 * @version $JOMC: DefaultModletProvider.java 5043 2015-05-27 07:03:39Z schulte $
049 * @see ModelContext#findModlets(org.jomc.modlet.Modlets)
050 */
051public class DefaultModletProvider implements ModletProvider
052{
053
054    /**
055     * Constant for the name of the model context attribute backing property {@code enabled}.
056     *
057     * @see #findModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
058     * @see ModelContext#getAttribute(java.lang.String)
059     * @since 1.2
060     */
061    public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.modlet.DefaultModletProvider.enabledAttribute";
062
063    /**
064     * Constant for the name of the system property controlling property {@code defaultEnabled}.
065     *
066     * @see #isDefaultEnabled()
067     * @since 1.2
068     */
069    private static final String DEFAULT_ENABLED_PROPERTY_NAME =
070        "org.jomc.modlet.DefaultModletProvider.defaultEnabled";
071
072    /**
073     * Default value of the flag indicating the provider is enabled by default.
074     *
075     * @see #isDefaultEnabled()
076     * @since 1.2
077     */
078    private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
079
080    /**
081     * Flag indicating the provider is enabled by default.
082     */
083    private static volatile Boolean defaultEnabled;
084
085    /**
086     * Flag indicating the provider is enabled.
087     */
088    private Boolean enabled;
089
090    /**
091     * Constant for the name of the model context attribute backing property {@code modletLocation}.
092     *
093     * @see #findModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
094     * @see ModelContext#getAttribute(java.lang.String)
095     * @since 1.2
096     */
097    public static final String MODLET_LOCATION_ATTRIBUTE_NAME =
098        "org.jomc.modlet.DefaultModletProvider.modletLocationAttribute";
099
100    /**
101     * Constant for the name of the system property controlling property {@code defaultModletLocation}.
102     *
103     * @see #getDefaultModletLocation()
104     * @since 1.2
105     */
106    private static final String DEFAULT_MODLET_LOCATION_PROPERTY_NAME =
107        "org.jomc.modlet.DefaultModletProvider.defaultModletLocation";
108
109    /**
110     * Class path location searched for {@code Modlets} by default.
111     *
112     * @see #getDefaultModletLocation()
113     */
114    private static final String DEFAULT_MODLET_LOCATION = "META-INF/jomc-modlet.xml";
115
116    /**
117     * Default {@code Modlet} location.
118     */
119    private static volatile String defaultModletLocation;
120
121    /**
122     * Modlet location of the instance.
123     */
124    private String modletLocation;
125
126    /**
127     * Constant for the name of the model context attribute backing property {@code validating}.
128     *
129     * @see #findModlets(org.jomc.modlet.ModelContext, java.lang.String)
130     * @see ModelContext#getAttribute(java.lang.String)
131     * @since 1.2
132     */
133    public static final String VALIDATING_ATTRIBUTE_NAME =
134        "org.jomc.modlet.DefaultModletProvider.validatingAttribute";
135
136    /**
137     * Constant for the name of the system property controlling property {@code defaultValidating}.
138     *
139     * @see #isDefaultValidating()
140     * @since 1.2
141     */
142    private static final String DEFAULT_VALIDATING_PROPERTY_NAME =
143        "org.jomc.modlet.DefaultModletProvider.defaultValidating";
144
145    /**
146     * Default value of the flag indicating the provider is validating resources by default.
147     *
148     * @see #isDefaultValidating()
149     * @since 1.2
150     */
151    private static final Boolean DEFAULT_VALIDATING = Boolean.TRUE;
152
153    /**
154     * Flag indicating the provider is validating resources by default.
155     *
156     * @since 1.2
157     */
158    private static volatile Boolean defaultValidating;
159
160    /**
161     * Flag indicating the provider is validating resources.
162     *
163     * @since 1.2
164     */
165    private Boolean validating;
166
167    /**
168     * Constant for the name of the system property controlling property {@code defaultOrdinal}.
169     *
170     * @see #getDefaultOrdinal()
171     * @since 1.6
172     */
173    private static final String DEFAULT_ORDINAL_PROPERTY_NAME =
174        "org.jomc.modlet.DefaultModletProvider.defaultOrdinal";
175
176    /**
177     * Default value of the ordinal number of the provider.
178     *
179     * @see #getDefaultOrdinal()
180     * @since 1.6
181     */
182    private static final Integer DEFAULT_ORDINAL = 0;
183
184    /**
185     * Default ordinal number of the provider.
186     *
187     * @since 1.6
188     */
189    private static volatile Integer defaultOrdinal;
190
191    /**
192     * Ordinal number of the provider.
193     *
194     * @since 1.6
195     */
196    private Integer ordinal;
197
198    /**
199     * Creates a new {@code DefaultModletProvider} instance.
200     */
201    public DefaultModletProvider()
202    {
203        super();
204    }
205
206    /**
207     * Gets a flag indicating the provider is enabled by default.
208     * <p>
209     * The default enabled flag is controlled by system property
210     * {@code org.jomc.modlet.DefaultModletProvider.defaultEnabled} holding a value indicating the provider is
211     * enabled by default. If that property is not set, the {@code true} default is returned.
212     * </p>
213     *
214     * @return {@code true}, if the provider is enabled by default; {@code false}, if the provider is disabled by
215     * default.
216     *
217     * @see #isEnabled()
218     * @see #setDefaultEnabled(java.lang.Boolean)
219     */
220    public static boolean isDefaultEnabled()
221    {
222        if ( defaultEnabled == null )
223        {
224            defaultEnabled = Boolean.valueOf( System.getProperty(
225                DEFAULT_ENABLED_PROPERTY_NAME, Boolean.toString( DEFAULT_ENABLED ) ) );
226
227        }
228
229        return defaultEnabled;
230    }
231
232    /**
233     * Sets the flag indicating the provider is enabled by default.
234     *
235     * @param value The new value of the flag indicating the provider is enabled by default or {@code null}.
236     *
237     * @see #isDefaultEnabled()
238     */
239    public static void setDefaultEnabled( final Boolean value )
240    {
241        defaultEnabled = value;
242    }
243
244    /**
245     * Gets a flag indicating the provider is enabled.
246     *
247     * @return {@code true}, if the provider is enabled; {@code false}, if the provider is disabled.
248     *
249     * @see #isDefaultEnabled()
250     * @see #setEnabled(java.lang.Boolean)
251     */
252    public final boolean isEnabled()
253    {
254        if ( this.enabled == null )
255        {
256            this.enabled = isDefaultEnabled();
257        }
258
259        return this.enabled;
260    }
261
262    /**
263     * Sets the flag indicating the provider is enabled.
264     *
265     * @param value The new value of the flag indicating the provider is enabled or {@code null}.
266     *
267     * @see #isEnabled()
268     */
269    public final void setEnabled( final Boolean value )
270    {
271        this.enabled = value;
272    }
273
274    /**
275     * Gets the default location searched for {@code Modlet} resources.
276     * <p>
277     * The default {@code Modlet} location is controlled by system property
278     * {@code org.jomc.modlet.DefaultModletProvider.defaultModletLocation} holding the location to search for
279     * {@code Modlet} resources by default. If that property is not set, the {@code META-INF/jomc-modlet.xml} default is
280     * returned.
281     * </p>
282     *
283     * @return The location searched for {@code Modlet} resources by default.
284     *
285     * @see #setDefaultModletLocation(java.lang.String)
286     */
287    public static String getDefaultModletLocation()
288    {
289        if ( defaultModletLocation == null )
290        {
291            defaultModletLocation = System.getProperty(
292                DEFAULT_MODLET_LOCATION_PROPERTY_NAME, DEFAULT_MODLET_LOCATION );
293
294        }
295
296        return defaultModletLocation;
297    }
298
299    /**
300     * Sets the default location searched for {@code Modlet} resources.
301     *
302     * @param value The new default location to search for {@code Modlet} resources or {@code null}.
303     *
304     * @see #getDefaultModletLocation()
305     */
306    public static void setDefaultModletLocation( final String value )
307    {
308        defaultModletLocation = value;
309    }
310
311    /**
312     * Gets the location searched for {@code Modlet} resources.
313     *
314     * @return The location searched for {@code Modlet} resources.
315     *
316     * @see #getDefaultModletLocation()
317     * @see #setModletLocation(java.lang.String)
318     */
319    public final String getModletLocation()
320    {
321        if ( this.modletLocation == null )
322        {
323            this.modletLocation = getDefaultModletLocation();
324        }
325
326        return this.modletLocation;
327    }
328
329    /**
330     * Sets the location searched for {@code Modlet} resources.
331     *
332     * @param value The new location to search for {@code Modlet} resources or {@code null}.
333     *
334     * @see #getModletLocation()
335     */
336    public final void setModletLocation( final String value )
337    {
338        this.modletLocation = value;
339    }
340
341    /**
342     * Gets a flag indicating the provider is validating resources by default.
343     * <p>
344     * The default validating flag is controlled by system property
345     * {@code org.jomc.modlet.DefaultModletProvider.defaultValidating} holding a value indicating the provider is
346     * validating resources by default. If that property is not set, the {@code true} default is returned.
347     * </p>
348     *
349     * @return {@code true}, if the provider is validating resources by default; {@code false}, if the provider is not
350     * validating resources by default.
351     *
352     * @see #isValidating()
353     * @see #setDefaultValidating(java.lang.Boolean)
354     *
355     * @since 1.2
356     */
357    public static boolean isDefaultValidating()
358    {
359        if ( defaultValidating == null )
360        {
361            defaultValidating = Boolean.valueOf( System.getProperty(
362                DEFAULT_VALIDATING_PROPERTY_NAME, Boolean.toString( DEFAULT_VALIDATING ) ) );
363
364        }
365
366        return defaultValidating;
367    }
368
369    /**
370     * Sets the flag indicating the provider is validating resources by default.
371     *
372     * @param value The new value of the flag indicating the provider is validating resources by default or
373     * {@code null}.
374     *
375     * @see #isDefaultValidating()
376     *
377     * @since 1.2
378     */
379    public static void setDefaultValidating( final Boolean value )
380    {
381        defaultValidating = value;
382    }
383
384    /**
385     * Gets a flag indicating the provider is validating resources.
386     *
387     * @return {@code true}, if the provider is validating resources; {@code false}, if the provider is not validating
388     * resources.
389     *
390     * @see #isDefaultValidating()
391     * @see #setValidating(java.lang.Boolean)
392     *
393     * @since 1.2
394     */
395    public final boolean isValidating()
396    {
397        if ( this.validating == null )
398        {
399            this.validating = isDefaultValidating();
400        }
401
402        return this.validating;
403    }
404
405    /**
406     * Sets the flag indicating the provider is validating resources.
407     *
408     * @param value The new value of the flag indicating the provider is validating resources or {@code null}.
409     *
410     * @see #isValidating()
411     *
412     * @since 1.2
413     */
414    public final void setValidating( final Boolean value )
415    {
416        this.validating = value;
417    }
418
419    /**
420     * Gets the default ordinal number of the provider.
421     * <p>
422     * The default ordinal number is controlled by system property
423     * {@code org.jomc.modlet.DefaultModletProvider.defaultOrdinal} holding the default ordinal number of the provider.
424     * If that property is not set, the {@code 0} default is returned.
425     * </p>
426     *
427     * @return The default ordinal number of the provider.
428     *
429     * @see #setDefaultOrdinal(java.lang.Integer)
430     *
431     * @since 1.6
432     */
433    public static int getDefaultOrdinal()
434    {
435        if ( defaultOrdinal == null )
436        {
437            defaultOrdinal = Integer.getInteger( DEFAULT_ORDINAL_PROPERTY_NAME, DEFAULT_ORDINAL );
438        }
439
440        return defaultOrdinal;
441    }
442
443    /**
444     * Sets the default ordinal number of the provider.
445     *
446     * @param value The new default ordinal number of the provider or {@code null}.
447     *
448     * @see #getDefaultOrdinal()
449     *
450     * @since 1.6
451     */
452    public static void setDefaultOrdinal( final Integer value )
453    {
454        defaultOrdinal = value;
455    }
456
457    /**
458     * Gets the ordinal number of the provider.
459     *
460     * @return The ordinal number of the provider.
461     *
462     * @see #getDefaultOrdinal()
463     * @see #setOrdinal(java.lang.Integer)
464     *
465     * @since 1.6
466     */
467    public final int getOrdinal()
468    {
469        if ( this.ordinal == null )
470        {
471            this.ordinal = getDefaultOrdinal();
472        }
473
474        return this.ordinal;
475    }
476
477    /**
478     * Sets the ordinal number of the provider.
479     *
480     * @param value The new ordinal number of the provider or {@code null}.
481     *
482     * @see #getOrdinal()
483     *
484     * @since 1.6
485     */
486    public final void setOrdinal( final Integer value )
487    {
488        this.ordinal = value;
489    }
490
491    /**
492     * Searches a given context for {@code Modlets}.
493     *
494     * @param context The context to search for {@code Modlets}.
495     * @param location The location to search at.
496     *
497     * @return The {@code Modlets} found at {@code location} in {@code context} or {@code null}, if no {@code Modlets}
498     * are found.
499     *
500     * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
501     * @throws ModelException if searching the context fails.
502     *
503     * @see #isValidating()
504     * @see #VALIDATING_ATTRIBUTE_NAME
505     */
506    public Modlets findModlets( final ModelContext context, final String location ) throws ModelException
507    {
508        if ( context == null )
509        {
510            throw new NullPointerException( "context" );
511        }
512        if ( location == null )
513        {
514            throw new NullPointerException( "location" );
515        }
516
517        URL url = null;
518
519        try
520        {
521            boolean contextValidating = this.isValidating();
522            if ( DEFAULT_VALIDATING == contextValidating
523                     && context.getAttribute( VALIDATING_ATTRIBUTE_NAME ) instanceof Boolean )
524            {
525                contextValidating = (Boolean) context.getAttribute( VALIDATING_ATTRIBUTE_NAME );
526            }
527
528            Modlets modlets = null;
529            final long t0 = System.currentTimeMillis();
530            final JAXBContext ctx = context.createContext( ModletObject.MODEL_PUBLIC_ID );
531            final Unmarshaller u = ctx.createUnmarshaller();
532            final Enumeration<URL> e = context.findResources( location );
533
534            if ( contextValidating )
535            {
536                u.setSchema( context.createSchema( ModletObject.MODEL_PUBLIC_ID ) );
537            }
538
539            while ( e.hasMoreElements() )
540            {
541                url = e.nextElement();
542                Object content = u.unmarshal( url );
543                if ( content instanceof JAXBElement<?> )
544                {
545                    content = ( (JAXBElement<?>) content ).getValue();
546                }
547
548                if ( content instanceof Modlet )
549                {
550                    if ( modlets == null )
551                    {
552                        modlets = new Modlets();
553                    }
554
555                    modlets.getModlet().add( (Modlet) content );
556                }
557                else if ( content instanceof Modlets )
558                {
559                    if ( modlets == null )
560                    {
561                        modlets = new Modlets();
562                    }
563
564                    modlets.getModlet().addAll( ( (Modlets) content ).getModlet() );
565                }
566            }
567
568            if ( context.isLoggable( Level.FINE ) )
569            {
570                context.log( Level.FINE, getMessage( "contextReport",
571                                                     modlets != null ? modlets.getModlet().size() : 0,
572                                                     location, System.currentTimeMillis() - t0 ), null );
573
574            }
575
576            return modlets == null || modlets.getModlet().isEmpty() ? null : modlets;
577        }
578        catch ( final UnmarshalException e )
579        {
580            String message = getMessage( e );
581            if ( message == null && e.getLinkedException() != null )
582            {
583                message = getMessage( e.getLinkedException() );
584            }
585
586            if ( url != null )
587            {
588                message = getMessage( "unmarshalException", url.toExternalForm(),
589                                      message != null ? " " + message : "" );
590
591            }
592
593            throw new ModelException( message, e );
594        }
595        catch ( final JAXBException e )
596        {
597            String message = getMessage( e );
598            if ( message == null && e.getLinkedException() != null )
599            {
600                message = getMessage( e.getLinkedException() );
601            }
602
603            throw new ModelException( message, e );
604        }
605    }
606
607    /**
608     * {@inheritDoc}
609     *
610     * @return The {@code Modlets} found in the context or {@code null}, if no {@code Modlets} are found or the provider
611     * is disabled.
612     *
613     * @see #isEnabled()
614     * @see #getModletLocation()
615     * @see #findModlets(org.jomc.modlet.ModelContext, java.lang.String)
616     * @see #ENABLED_ATTRIBUTE_NAME
617     * @see #MODLET_LOCATION_ATTRIBUTE_NAME
618     * @deprecated As of JOMC 1.6, this method has been replaced by {@link #findModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)}.
619     * This method will be removed in JOMC 2.0.
620     */
621    @Deprecated
622    public Modlets findModlets( final ModelContext context ) throws ModelException
623    {
624        if ( context == null )
625        {
626            throw new NullPointerException( "context" );
627        }
628
629        return this.findModlets( context, new Modlets() );
630    }
631
632    /**
633     * {@inheritDoc}
634     *
635     * @return The {@code Modlets} found in the context or {@code null}, if no {@code Modlets} are found or the provider
636     * is disabled.
637     *
638     * @see #isEnabled()
639     * @see #getModletLocation()
640     * @see #findModlets(org.jomc.modlet.ModelContext, java.lang.String)
641     * @see #ENABLED_ATTRIBUTE_NAME
642     * @see #MODLET_LOCATION_ATTRIBUTE_NAME
643     * @since 1.6
644     */
645    public Modlets findModlets( final ModelContext context, final Modlets modlets ) throws ModelException
646    {
647        if ( context == null )
648        {
649            throw new NullPointerException( "context" );
650        }
651        if ( modlets == null )
652        {
653            throw new NullPointerException( "context" );
654        }
655
656        Modlets provided = null;
657
658        boolean contextEnabled = this.isEnabled();
659        if ( DEFAULT_ENABLED == contextEnabled && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
660        {
661            contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
662        }
663
664        String contextModletLocation = this.getModletLocation();
665        if ( DEFAULT_MODLET_LOCATION.equals( contextModletLocation )
666                 && context.getAttribute( MODLET_LOCATION_ATTRIBUTE_NAME ) instanceof String )
667        {
668            contextModletLocation = (String) context.getAttribute( MODLET_LOCATION_ATTRIBUTE_NAME );
669        }
670
671        if ( contextEnabled )
672        {
673            final Modlets found = this.findModlets( context, contextModletLocation );
674
675            if ( found != null )
676            {
677                provided = modlets.clone();
678                provided.getModlet().addAll( found.getModlet() );
679            }
680        }
681        else if ( context.isLoggable( Level.FINER ) )
682        {
683            context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName() ), null );
684        }
685
686        return provided;
687    }
688
689    private static String getMessage( final String key, final Object... arguments )
690    {
691        return MessageFormat.format( ResourceBundle.getBundle(
692            DefaultModletProvider.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
693
694    }
695
696    private static String getMessage( final Throwable t )
697    {
698        return t != null
699                   ? t.getMessage() != null && t.getMessage().trim().length() > 0
700                         ? t.getMessage()
701                         : getMessage( t.getCause() )
702                   : null;
703
704    }
705
706}