View Javadoc

1   /*
2    *   Copyright (C) Christian Schulte, 2005-206
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: DefaultModelProcessor.java 4760 2013-04-08 17:56:26Z schulte $
29   *
30   */
31  package org.jomc.model.modlet;
32  
33  import java.net.URISyntaxException;
34  import java.net.URL;
35  import java.text.MessageFormat;
36  import java.util.Enumeration;
37  import java.util.LinkedList;
38  import java.util.List;
39  import java.util.Locale;
40  import java.util.Map;
41  import java.util.ResourceBundle;
42  import java.util.logging.Level;
43  import javax.xml.bind.JAXBContext;
44  import javax.xml.bind.JAXBElement;
45  import javax.xml.bind.JAXBException;
46  import javax.xml.bind.util.JAXBResult;
47  import javax.xml.bind.util.JAXBSource;
48  import javax.xml.transform.ErrorListener;
49  import javax.xml.transform.Transformer;
50  import javax.xml.transform.TransformerConfigurationException;
51  import javax.xml.transform.TransformerException;
52  import javax.xml.transform.TransformerFactory;
53  import javax.xml.transform.stream.StreamSource;
54  import org.jomc.modlet.Model;
55  import org.jomc.modlet.ModelContext;
56  import org.jomc.modlet.ModelException;
57  import org.jomc.modlet.ModelProcessor;
58  
59  /**
60   * Default object management and configuration {@code ModelProcessor} implementation.
61   *
62   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
63   * @version $JOMC: DefaultModelProcessor.java 4760 2013-04-08 17:56:26Z schulte $
64   * @see ModelContext#processModel(org.jomc.modlet.Model)
65   */
66  public class DefaultModelProcessor implements ModelProcessor
67  {
68  
69      /**
70       * Constant for the name of the model context attribute backing property {@code enabled}.
71       * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
72       * @see ModelContext#getAttribute(java.lang.String)
73       * @since 1.2
74       */
75      public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProcessor.enabledAttribute";
76  
77      /**
78       * Constant for the name of the system property controlling property {@code defaultEnabled}.
79       * @see #isDefaultEnabled()
80       */
81      private static final String DEFAULT_ENABLED_PROPERTY_NAME =
82          "org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled";
83  
84      /**
85       * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}.
86       * @see #isDefaultEnabled()
87       */
88      private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME =
89          "org.jomc.model.DefaultModelProcessor.defaultEnabled";
90  
91      /**
92       * Default value of the flag indicating the processor is enabled by default.
93       * @see #isDefaultEnabled()
94       * @since 1.2
95       */
96      private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
97  
98      /** Flag indicating the processor is enabled by default. */
99      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
501                ? t.getMessage() != null && t.getMessage().trim().length() > 0
502                  ? t.getMessage()
503                  : getMessage( t.getCause() )
504                : null;
505 
506     }
507 
508 }