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