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     */
031    package org.jomc.modlet;
032    
033    import java.net.URL;
034    import java.text.MessageFormat;
035    import java.util.Enumeration;
036    import java.util.ResourceBundle;
037    import java.util.logging.Level;
038    import javax.xml.bind.JAXBContext;
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    
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     */
051    public 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    }