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