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: DefaultModletProvider.java 4203 2012-01-26 08:32:45Z schulte2005 $
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:schulte2005@users.sourceforge.net">Christian Schulte</a>
048 * @version $JOMC: DefaultModletProvider.java 4203 2012-01-26 08:32:45Z schulte2005 $
049 * @see ModelContext#findModlets()
050 */
051public class DefaultModletProvider implements ModletProvider
052{
053
054    /**
055     * Constant for the name of the model context attribute backing property {@code enabled}.
056     * @see #findModlets(org.jomc.modlet.ModelContext)
057     * @see ModelContext#getAttribute(java.lang.String)
058     * @since 1.2
059     */
060    public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.modlet.DefaultModletProvider.enabledAttribute";
061
062    /**
063     * Constant for the name of the system property controlling property {@code defaultEnabled}.
064     * @see #isDefaultEnabled()
065     * @since 1.2
066     */
067    private static final String DEFAULT_ENABLED_PROPERTY_NAME =
068        "org.jomc.modlet.DefaultModletProvider.defaultEnabled";
069
070    /**
071     * Default value of the flag indicating the provider is enabled by default.
072     * @see #isDefaultEnabled()
073     * @since 1.2
074     */
075    private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
076
077    /** Flag indicating the provider is enabled by default. */
078    private static volatile Boolean defaultEnabled;
079
080    /** Flag indicating the provider is enabled. */
081    private Boolean enabled;
082
083    /**
084     * Constant for the name of the model context attribute backing property {@code modletLocation}.
085     * @see #findModlets(org.jomc.modlet.ModelContext)
086     * @see ModelContext#getAttribute(java.lang.String)
087     * @since 1.2
088     */
089    public static final String MODLET_LOCATION_ATTRIBUTE_NAME =
090        "org.jomc.modlet.DefaultModletProvider.modletLocationAttribute";
091
092    /**
093     * Constant for the name of the system property controlling property {@code defaultModletLocation}.
094     * @see #getDefaultModletLocation()
095     * @since 1.2
096     */
097    private static final String DEFAULT_MODLET_LOCATION_PROPERTY_NAME =
098        "org.jomc.modlet.DefaultModletProvider.defaultModletLocation";
099
100    /**
101     * Class path location searched for {@code Modlets} by default.
102     * @see #getDefaultModletLocation()
103     */
104    private static final String DEFAULT_MODLET_LOCATION = "META-INF/jomc-modlet.xml";
105
106    /** Default {@code Modlet} location. */
107    private static volatile String defaultModletLocation;
108
109    /** Modlet location of the instance. */
110    private String modletLocation;
111
112    /**
113     * Constant for the name of the model context attribute backing property {@code validating}.
114     * @see #findModlets(org.jomc.modlet.ModelContext, java.lang.String)
115     * @see ModelContext#getAttribute(java.lang.String)
116     * @since 1.2
117     */
118    public static final String VALIDATING_ATTRIBUTE_NAME =
119        "org.jomc.modlet.DefaultModletProvider.validatingAttribute";
120
121    /**
122     * Constant for the name of the system property controlling property {@code defaultValidating}.
123     * @see #isDefaultValidating()
124     * @since 1.2
125     */
126    private static final String DEFAULT_VALIDATING_PROPERTY_NAME =
127        "org.jomc.modlet.DefaultModletProvider.defaultValidating";
128
129    /**
130     * Default value of the flag indicating the provider is validating resources by default.
131     * @see #isDefaultValidating()
132     * @since 1.2
133     */
134    private static final Boolean DEFAULT_VALIDATING = Boolean.TRUE;
135
136    /**
137     * Flag indicating the provider is validating resources by default.
138     * @since 1.2
139     */
140    private static volatile Boolean defaultValidating;
141
142    /**
143     * Flag indicating the provider is validating resources.
144     * @since 1.2
145     */
146    private Boolean validating;
147
148    /** Creates a new {@code DefaultModletProvider} instance. */
149    public DefaultModletProvider()
150    {
151        super();
152    }
153
154    /**
155     * Gets a flag indicating the provider is enabled by default.
156     * <p>The default enabled flag is controlled by system property
157     * {@code org.jomc.modlet.DefaultModletProvider.defaultEnabled} holding a value indicating the provider is
158     * enabled by default. If that property is not set, the {@code true} default is returned.</p>
159     *
160     * @return {@code true}, if the provider is enabled by default; {@code false}, if the provider is disabled by
161     * default.
162     *
163     * @see #isEnabled()
164     * @see #setDefaultEnabled(java.lang.Boolean)
165     */
166    public static boolean isDefaultEnabled()
167    {
168        if ( defaultEnabled == null )
169        {
170            defaultEnabled = Boolean.valueOf( System.getProperty(
171                DEFAULT_ENABLED_PROPERTY_NAME, Boolean.toString( DEFAULT_ENABLED ) ) );
172
173        }
174
175        return defaultEnabled;
176    }
177
178    /**
179     * Sets the flag indicating the provider is enabled by default.
180     *
181     * @param value The new value of the flag indicating the provider is enabled by default or {@code null}.
182     *
183     * @see #isDefaultEnabled()
184     */
185    public static void setDefaultEnabled( final Boolean value )
186    {
187        defaultEnabled = value;
188    }
189
190    /**
191     * Gets a flag indicating the provider is enabled.
192     *
193     * @return {@code true}, if the provider is enabled; {@code false}, if the provider is disabled.
194     *
195     * @see #isDefaultEnabled()
196     * @see #setEnabled(java.lang.Boolean)
197     */
198    public final boolean isEnabled()
199    {
200        if ( this.enabled == null )
201        {
202            this.enabled = isDefaultEnabled();
203        }
204
205        return this.enabled;
206    }
207
208    /**
209     * Sets the flag indicating the provider is enabled.
210     *
211     * @param value The new value of the flag indicating the provider is enabled or {@code null}.
212     *
213     * @see #isEnabled()
214     */
215    public final void setEnabled( final Boolean value )
216    {
217        this.enabled = value;
218    }
219
220    /**
221     * Gets the default location searched for {@code Modlet} resources.
222     * <p>The default {@code Modlet} location is controlled by system property
223     * {@code org.jomc.modlet.DefaultModletProvider.defaultModletLocation} holding the location to search for
224     * {@code Modlet} resources by default. If that property is not set, the {@code META-INF/jomc-modlet.xml} default is
225     * returned.</p>
226     *
227     * @return The location searched for {@code Modlet} resources by default.
228     *
229     * @see #setDefaultModletLocation(java.lang.String)
230     */
231    public static String getDefaultModletLocation()
232    {
233        if ( defaultModletLocation == null )
234        {
235            defaultModletLocation = System.getProperty(
236                DEFAULT_MODLET_LOCATION_PROPERTY_NAME, DEFAULT_MODLET_LOCATION );
237
238        }
239
240        return defaultModletLocation;
241    }
242
243    /**
244     * Sets the default location searched for {@code Modlet} resources.
245     *
246     * @param value The new default location to search for {@code Modlet} resources or {@code null}.
247     *
248     * @see #getDefaultModletLocation()
249     */
250    public static void setDefaultModletLocation( final String value )
251    {
252        defaultModletLocation = value;
253    }
254
255    /**
256     * Gets the location searched for {@code Modlet} resources.
257     *
258     * @return The location searched for {@code Modlet} resources.
259     *
260     * @see #getDefaultModletLocation()
261     * @see #setModletLocation(java.lang.String)
262     */
263    public final String getModletLocation()
264    {
265        if ( this.modletLocation == null )
266        {
267            this.modletLocation = getDefaultModletLocation();
268        }
269
270        return this.modletLocation;
271    }
272
273    /**
274     * Sets the location searched for {@code Modlet} resources.
275     *
276     * @param value The new location to search for {@code Modlet} resources or {@code null}.
277     *
278     * @see #getModletLocation()
279     */
280    public final void setModletLocation( final String value )
281    {
282        this.modletLocation = value;
283    }
284
285    /**
286     * Gets a flag indicating the provider is validating resources by default.
287     * <p>The default validating flag is controlled by system property
288     * {@code org.jomc.modlet.DefaultModletProvider.defaultValidating} holding a value indicating the provider is
289     * validating resources by default. If that property is not set, the {@code true} default is returned.</p>
290     *
291     * @return {@code true}, if the provider is validating resources by default; {@code false}, if the provider is not
292     * validating resources by default.
293     *
294     * @see #isValidating()
295     * @see #setDefaultValidating(java.lang.Boolean)
296     *
297     * @since 1.2
298     */
299    public static boolean isDefaultValidating()
300    {
301        if ( defaultValidating == null )
302        {
303            defaultValidating = Boolean.valueOf( System.getProperty(
304                DEFAULT_VALIDATING_PROPERTY_NAME, Boolean.toString( DEFAULT_VALIDATING ) ) );
305
306        }
307
308        return defaultValidating;
309    }
310
311    /**
312     * Sets the flag indicating the provider is validating resources by default.
313     *
314     * @param value The new value of the flag indicating the provider is validating resources by default or
315     * {@code null}.
316     *
317     * @see #isDefaultValidating()
318     *
319     * @since 1.2
320     */
321    public static void setDefaultValidating( final Boolean value )
322    {
323        defaultValidating = value;
324    }
325
326    /**
327     * Gets a flag indicating the provider is validating resources.
328     *
329     * @return {@code true}, if the provider is validating resources; {@code false}, if the provider is not validating
330     * resources.
331     *
332     * @see #isDefaultValidating()
333     * @see #setValidating(java.lang.Boolean)
334     *
335     * @since 1.2
336     */
337    public final boolean isValidating()
338    {
339        if ( this.validating == null )
340        {
341            this.validating = isDefaultValidating();
342        }
343
344        return this.validating;
345    }
346
347    /**
348     * Sets the flag indicating the provider is validating resources.
349     *
350     * @param value The new value of the flag indicating the provider is validating resources or {@code null}.
351     *
352     * @see #isValidating()
353     *
354     * @since 1.2
355     */
356    public final void setValidating( final Boolean value )
357    {
358        this.validating = value;
359    }
360
361    /**
362     * Searches a given context for {@code Modlets}.
363     *
364     * @param context The context to search for {@code Modlets}.
365     * @param location The location to search at.
366     *
367     * @return The {@code Modlets} found at {@code location} in {@code context} or {@code null}, if no {@code Modlets}
368     * are found.
369     *
370     * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
371     * @throws ModelException if searching the context fails.
372     *
373     * @see #isValidating()
374     * @see #VALIDATING_ATTRIBUTE_NAME
375     */
376    public Modlets findModlets( final ModelContext context, final String location ) throws ModelException
377    {
378        if ( context == null )
379        {
380            throw new NullPointerException( "context" );
381        }
382        if ( location == null )
383        {
384            throw new NullPointerException( "location" );
385        }
386
387        URL url = null;
388
389        try
390        {
391            boolean contextValidating = this.isValidating();
392            if ( DEFAULT_VALIDATING == contextValidating
393                 && context.getAttribute( VALIDATING_ATTRIBUTE_NAME ) instanceof Boolean )
394            {
395                contextValidating = (Boolean) context.getAttribute( VALIDATING_ATTRIBUTE_NAME );
396            }
397
398            Modlets modlets = null;
399            final long t0 = System.currentTimeMillis();
400            final JAXBContext ctx = context.createContext( ModletObject.MODEL_PUBLIC_ID );
401            final Unmarshaller u = ctx.createUnmarshaller();
402            final Enumeration<URL> e = context.findResources( location );
403
404            if ( contextValidating )
405            {
406                u.setSchema( context.createSchema( ModletObject.MODEL_PUBLIC_ID ) );
407            }
408
409            while ( e.hasMoreElements() )
410            {
411                url = e.nextElement();
412                Object content = u.unmarshal( url );
413                if ( content instanceof JAXBElement<?> )
414                {
415                    content = ( (JAXBElement<?>) content ).getValue();
416                }
417
418                if ( content instanceof Modlet )
419                {
420                    if ( modlets == null )
421                    {
422                        modlets = new Modlets();
423                    }
424
425                    modlets.getModlet().add( (Modlet) content );
426                }
427                else if ( content instanceof Modlets )
428                {
429                    if ( modlets == null )
430                    {
431                        modlets = new Modlets();
432                    }
433
434                    modlets.getModlet().addAll( ( (Modlets) content ).getModlet() );
435                }
436            }
437
438            if ( context.isLoggable( Level.FINE ) )
439            {
440                context.log( Level.FINE, getMessage( "contextReport",
441                                                     modlets != null ? modlets.getModlet().size() : 0,
442                                                     location, System.currentTimeMillis() - t0 ), null );
443
444            }
445
446            return modlets == null || modlets.getModlet().isEmpty() ? null : modlets;
447        }
448        catch ( final UnmarshalException e )
449        {
450            String message = getMessage( e );
451            if ( message == null && e.getLinkedException() != null )
452            {
453                message = getMessage( e.getLinkedException() );
454            }
455
456            if ( url != null )
457            {
458                message = getMessage( "unmarshalException", url.toExternalForm(),
459                                      message != null ? " " + message : "" );
460
461            }
462
463            throw new ModelException( message, e );
464        }
465        catch ( final JAXBException e )
466        {
467            String message = getMessage( e );
468            if ( message == null && e.getLinkedException() != null )
469            {
470                message = getMessage( e.getLinkedException() );
471            }
472
473            throw new ModelException( message, e );
474        }
475    }
476
477    /**
478     * {@inheritDoc}
479     *
480     * @return The {@code Modlets} found in the context or {@code null}, if no {@code Modlets} are found or the provider
481     * is disabled.
482     *
483     * @see #isEnabled()
484     * @see #getModletLocation()
485     * @see #findModlets(org.jomc.modlet.ModelContext, java.lang.String)
486     * @see #ENABLED_ATTRIBUTE_NAME
487     * @see #MODLET_LOCATION_ATTRIBUTE_NAME
488     */
489    public Modlets findModlets( final ModelContext context ) throws ModelException
490    {
491        if ( context == null )
492        {
493            throw new NullPointerException( "context" );
494        }
495
496        Modlets found = null;
497
498        boolean contextEnabled = this.isEnabled();
499        if ( DEFAULT_ENABLED == contextEnabled && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
500        {
501            contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
502        }
503
504        String contextModletLocation = this.getModletLocation();
505        if ( DEFAULT_MODLET_LOCATION.equals( contextModletLocation )
506             && context.getAttribute( MODLET_LOCATION_ATTRIBUTE_NAME ) instanceof String )
507        {
508            contextModletLocation = (String) context.getAttribute( MODLET_LOCATION_ATTRIBUTE_NAME );
509        }
510
511        if ( contextEnabled )
512        {
513            found = this.findModlets( context, contextModletLocation );
514        }
515        else if ( context.isLoggable( Level.FINER ) )
516        {
517            context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName() ), null );
518        }
519
520        return found;
521    }
522
523    private static String getMessage( final String key, final Object... arguments )
524    {
525        return MessageFormat.format( ResourceBundle.getBundle(
526            DefaultModletProvider.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
527
528    }
529
530    private static String getMessage( final Throwable t )
531    {
532        return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
533    }
534
535}