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: DefaultModelProcessor.java 5043 2015-05-27 07:03:39Z schulte $
029 *
030 */
031package org.jomc.model.modlet;
032
033import java.net.URISyntaxException;
034import java.net.URL;
035import java.text.MessageFormat;
036import java.util.Enumeration;
037import java.util.LinkedList;
038import java.util.List;
039import java.util.Locale;
040import java.util.Map;
041import java.util.ResourceBundle;
042import java.util.logging.Level;
043import javax.xml.bind.JAXBContext;
044import javax.xml.bind.JAXBElement;
045import javax.xml.bind.JAXBException;
046import javax.xml.bind.util.JAXBResult;
047import javax.xml.bind.util.JAXBSource;
048import javax.xml.transform.ErrorListener;
049import javax.xml.transform.Transformer;
050import javax.xml.transform.TransformerConfigurationException;
051import javax.xml.transform.TransformerException;
052import javax.xml.transform.TransformerFactory;
053import javax.xml.transform.stream.StreamSource;
054import org.jomc.modlet.Model;
055import org.jomc.modlet.ModelContext;
056import org.jomc.modlet.ModelException;
057import org.jomc.modlet.ModelProcessor;
058
059/**
060 * Default object management and configuration {@code ModelProcessor} implementation.
061 *
062 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
063 * @version $JOMC: DefaultModelProcessor.java 5043 2015-05-27 07:03:39Z schulte $
064 * @see ModelContext#processModel(org.jomc.modlet.Model)
065 */
066public class DefaultModelProcessor implements ModelProcessor
067{
068
069    /**
070     * Constant for the name of the model context attribute backing property {@code enabled}.
071     *
072     * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
073     * @see ModelContext#getAttribute(java.lang.String)
074     * @since 1.2
075     */
076    public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProcessor.enabledAttribute";
077
078    /**
079     * Constant for the name of the system property controlling property {@code defaultEnabled}.
080     *
081     * @see #isDefaultEnabled()
082     */
083    private static final String DEFAULT_ENABLED_PROPERTY_NAME =
084        "org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled";
085
086    /**
087     * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}.
088     * @see #isDefaultEnabled()
089     */
090    private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME =
091        "org.jomc.model.DefaultModelProcessor.defaultEnabled";
092
093    /**
094     * Default value of the flag indicating the processor is enabled by default.
095     *
096     * @see #isDefaultEnabled()
097     * @since 1.2
098     */
099    private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
100
101    /**
102     * Flag indicating the processor is enabled by default.
103     */
104    private static volatile Boolean defaultEnabled;
105
106    /**
107     * Flag indicating the processor is enabled.
108     */
109    private Boolean enabled;
110
111    /**
112     * Constant for the name of the model context attribute backing property {@code transformerLocation}.
113     *
114     * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
115     * @see ModelContext#getAttribute(java.lang.String)
116     * @since 1.2
117     */
118    public static final String TRANSFORMER_LOCATION_ATTRIBUTE_NAME =
119        "org.jomc.model.modlet.DefaultModelProcessor.transformerLocationAttribute";
120
121    /**
122     * Constant for the name of the system property controlling property {@code defaultTransformerLocation}.
123     *
124     * @see #getDefaultTransformerLocation()
125     */
126    private static final String DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME =
127        "org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation";
128
129    /**
130     * Constant for the name of the deprecated system property controlling property {@code defaultTransformerLocation}.
131     * @see #getDefaultTransformerLocation()
132     */
133    private static final String DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME =
134        "org.jomc.model.DefaultModelProcessor.defaultTransformerLocation";
135
136    /**
137     * Class path location searched for transformers by default.
138     *
139     * @see #getDefaultTransformerLocation()
140     */
141    private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc.xsl";
142
143    /**
144     * Default transformer location.
145     */
146    private static volatile String defaultTransformerLocation;
147
148    /**
149     * Transformer location of the instance.
150     */
151    private String transformerLocation;
152
153    /**
154     * Creates a new {@code DefaultModelProcessor} instance.
155     */
156    public DefaultModelProcessor()
157    {
158        super();
159    }
160
161    /**
162     * Gets a flag indicating the processor is enabled by default.
163     * <p>
164     * The default enabled flag is controlled by system property
165     * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled} holding a value indicating the processor is
166     * enabled by default. If that property is not set, the {@code true} default is returned.
167     * </p>
168     *
169     * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by
170     * default.
171     *
172     * @see #setDefaultEnabled(java.lang.Boolean)
173     */
174    public static boolean isDefaultEnabled()
175    {
176        if ( defaultEnabled == null )
177        {
178            defaultEnabled =
179                Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME,
180                                                     System.getProperty( DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME,
181                                                                         Boolean.toString( DEFAULT_ENABLED ) ) ) );
182
183        }
184
185        return defaultEnabled;
186    }
187
188    /**
189     * Sets the flag indicating the processor is enabled by default.
190     *
191     * @param value The new value of the flag indicating the processor is enabled by default or {@code null}.
192     *
193     * @see #isDefaultEnabled()
194     */
195    public static void setDefaultEnabled( final Boolean value )
196    {
197        defaultEnabled = value;
198    }
199
200    /**
201     * Gets a flag indicating the processor is enabled.
202     *
203     * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled.
204     *
205     * @see #isDefaultEnabled()
206     * @see #setEnabled(java.lang.Boolean)
207     */
208    public final boolean isEnabled()
209    {
210        if ( this.enabled == null )
211        {
212            this.enabled = isDefaultEnabled();
213        }
214
215        return this.enabled;
216    }
217
218    /**
219     * Sets the flag indicating the processor is enabled.
220     *
221     * @param value The new value of the flag indicating the processor is enabled or {@code null}.
222     *
223     * @see #isEnabled()
224     */
225    public final void setEnabled( final Boolean value )
226    {
227        this.enabled = value;
228    }
229
230    /**
231     * Gets the default location searched for transformer resources.
232     * <p>
233     * The default transformer location is controlled by system property
234     * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation} holding the location to search for
235     * transformer resources by default. If that property is not set, the {@code META-INF/jomc.xsl} default is
236     * returned.
237     * </p>
238     *
239     * @return The location searched for transformer resources by default.
240     *
241     * @see #setDefaultTransformerLocation(java.lang.String)
242     */
243    public static String getDefaultTransformerLocation()
244    {
245        if ( defaultTransformerLocation == null )
246        {
247            defaultTransformerLocation =
248                System.getProperty( DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME,
249                                    System.getProperty( DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME,
250                                                        DEFAULT_TRANSFORMER_LOCATION ) );
251
252        }
253
254        return defaultTransformerLocation;
255    }
256
257    /**
258     * Sets the default location searched for transformer resources.
259     *
260     * @param value The new default location to search for transformer resources or {@code null}.
261     *
262     * @see #getDefaultTransformerLocation()
263     */
264    public static void setDefaultTransformerLocation( final String value )
265    {
266        defaultTransformerLocation = value;
267    }
268
269    /**
270     * Gets the location searched for transformer resources.
271     *
272     * @return The location searched for transformer resources.
273     *
274     * @see #getDefaultTransformerLocation()
275     * @see #setTransformerLocation(java.lang.String)
276     */
277    public final String getTransformerLocation()
278    {
279        if ( this.transformerLocation == null )
280        {
281            this.transformerLocation = getDefaultTransformerLocation();
282        }
283
284        return this.transformerLocation;
285    }
286
287    /**
288     * Sets the location searched for transformer resources.
289     *
290     * @param value The new location to search for transformer resources or {@code null}.
291     *
292     * @see #getTransformerLocation()
293     */
294    public final void setTransformerLocation( final String value )
295    {
296        this.transformerLocation = value;
297    }
298
299    /**
300     * Searches a given context for transformers.
301     *
302     * @param context The context to search for transformers.
303     * @param location The location to search at.
304     *
305     * @return The transformers found at {@code location} in {@code context} or {@code null}, if no transformers are
306     * found.
307     *
308     * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
309     * @throws ModelException if getting the transformers fails.
310     */
311    public List<Transformer> findTransformers( final ModelContext context, final String location ) throws ModelException
312    {
313        if ( context == null )
314        {
315            throw new NullPointerException( "context" );
316        }
317        if ( location == null )
318        {
319            throw new NullPointerException( "location" );
320        }
321
322        try
323        {
324            final long t0 = System.currentTimeMillis();
325            final List<Transformer> transformers = new LinkedList<Transformer>();
326            final TransformerFactory transformerFactory = TransformerFactory.newInstance();
327            final Enumeration<URL> resources = context.findResources( location );
328            final ErrorListener errorListener = new ErrorListener()
329            {
330
331                public void warning( final TransformerException exception ) throws TransformerException
332                {
333                    if ( context.isLoggable( Level.WARNING ) )
334                    {
335                        context.log( Level.WARNING, getMessage( exception ), exception );
336                    }
337                }
338
339                public void error( final TransformerException exception ) throws TransformerException
340                {
341                    if ( context.isLoggable( Level.SEVERE ) )
342                    {
343                        context.log( Level.SEVERE, getMessage( exception ), exception );
344                    }
345
346                    throw exception;
347                }
348
349                public void fatalError( final TransformerException exception ) throws TransformerException
350                {
351                    if ( context.isLoggable( Level.SEVERE ) )
352                    {
353                        context.log( Level.SEVERE, getMessage( exception ), exception );
354                    }
355
356                    throw exception;
357                }
358
359            };
360
361            transformerFactory.setErrorListener( errorListener );
362
363            int count = 0;
364            while ( resources.hasMoreElements() )
365            {
366                count++;
367                final URL url = resources.nextElement();
368
369                if ( context.isLoggable( Level.FINEST ) )
370                {
371                    context.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null );
372                }
373
374                final Transformer transformer =
375                    transformerFactory.newTransformer( new StreamSource( url.toURI().toASCIIString() ) );
376
377                transformer.setErrorListener( errorListener );
378
379                for ( final Map.Entry<Object, Object> e : System.getProperties().entrySet() )
380                {
381                    transformer.setParameter( e.getKey().toString(), e.getValue() );
382                }
383
384                transformers.add( transformer );
385            }
386
387            if ( context.isLoggable( Level.FINE ) )
388            {
389                context.log( Level.FINE, getMessage( "contextReport", count, location,
390                                                     Long.valueOf( System.currentTimeMillis() - t0 ) ), null );
391
392            }
393
394            return transformers.isEmpty() ? null : transformers;
395        }
396        catch ( final URISyntaxException e )
397        {
398            throw new ModelException( getMessage( e ), e );
399        }
400        catch ( final TransformerConfigurationException e )
401        {
402            String message = getMessage( e );
403            if ( message == null && e.getException() != null )
404            {
405                message = getMessage( e.getException() );
406            }
407
408            throw new ModelException( message, e );
409        }
410    }
411
412    /**
413     * {@inheritDoc}
414     *
415     * @see #isEnabled()
416     * @see #getTransformerLocation()
417     * @see #findTransformers(org.jomc.modlet.ModelContext, java.lang.String)
418     * @see #ENABLED_ATTRIBUTE_NAME
419     * @see #TRANSFORMER_LOCATION_ATTRIBUTE_NAME
420     */
421    public Model processModel( final ModelContext context, final Model model ) throws ModelException
422    {
423        if ( context == null )
424        {
425            throw new NullPointerException( "context" );
426        }
427        if ( model == null )
428        {
429            throw new NullPointerException( "model" );
430        }
431
432        try
433        {
434            Model processed = model;
435
436            boolean contextEnabled = this.isEnabled();
437            if ( DEFAULT_ENABLED == contextEnabled
438                     && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
439            {
440                contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
441            }
442
443            String contextTransformerLocation = this.getTransformerLocation();
444            if ( DEFAULT_TRANSFORMER_LOCATION.equals( contextTransformerLocation )
445                     && context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
446            {
447                contextTransformerLocation = (String) context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME );
448            }
449
450            if ( contextEnabled )
451            {
452                final org.jomc.modlet.ObjectFactory objectFactory = new org.jomc.modlet.ObjectFactory();
453                final JAXBContext jaxbContext = context.createContext( model.getIdentifier() );
454                final List<Transformer> transformers = this.findTransformers( context, contextTransformerLocation );
455                processed = model.clone();
456
457                if ( transformers != null )
458                {
459                    for ( int i = 0, s0 = transformers.size(); i < s0; i++ )
460                    {
461                        final JAXBElement<Model> e = objectFactory.createModel( processed );
462                        final JAXBSource source = new JAXBSource( jaxbContext, e );
463                        final JAXBResult result = new JAXBResult( jaxbContext );
464                        transformers.get( i ).transform( source, result );
465
466                        if ( result.getResult() instanceof JAXBElement<?>
467                                 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Model )
468                        {
469                            processed = (Model) ( (JAXBElement<?>) result.getResult() ).getValue();
470                        }
471                        else
472                        {
473                            throw new ModelException( getMessage(
474                                "illegalTransformationResult", model.getIdentifier() ) );
475
476                        }
477                    }
478                }
479            }
480            else if ( context.isLoggable( Level.FINER ) )
481            {
482                context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(),
483                                                      model.getIdentifier() ), null );
484
485            }
486
487            return processed;
488        }
489        catch ( final TransformerException e )
490        {
491            String message = getMessage( e );
492            if ( message == null && e.getException() != null )
493            {
494                message = getMessage( e.getException() );
495            }
496
497            throw new ModelException( message, e );
498        }
499        catch ( final JAXBException e )
500        {
501            String message = getMessage( e );
502            if ( message == null && e.getLinkedException() != null )
503            {
504                message = getMessage( e.getLinkedException() );
505            }
506
507            throw new ModelException( message, e );
508        }
509    }
510
511    private static String getMessage( final String key, final Object... args )
512    {
513        return MessageFormat.format( ResourceBundle.getBundle(
514            DefaultModelProcessor.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
515
516    }
517
518    private static String getMessage( final Throwable t )
519    {
520        return t != null
521                   ? t.getMessage() != null && t.getMessage().trim().length() > 0
522                         ? t.getMessage()
523                         : getMessage( t.getCause() )
524                   : null;
525
526    }
527
528}