View Javadoc
1   /*
2    *   Copyright (C) Christian Schulte <cs@schulte.it>, 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 5043 2015-05-27 07:03:39Z 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 5043 2015-05-27 07:03:39Z 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       *
72       * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
73       * @see ModelContext#getAttribute(java.lang.String)
74       * @since 1.2
75       */
76      public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProcessor.enabledAttribute";
77  
78      /**
79       * Constant for the name of the system property controlling property {@code defaultEnabled}.
80       *
81       * @see #isDefaultEnabled()
82       */
83      private static final String DEFAULT_ENABLED_PROPERTY_NAME =
84          "org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled";
85  
86      /**
87       * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}.
88       * @see #isDefaultEnabled()
89       */
90      private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME =
91          "org.jomc.model.DefaultModelProcessor.defaultEnabled";
92  
93      /**
94       * Default value of the flag indicating the processor is enabled by default.
95       *
96       * @see #isDefaultEnabled()
97       * @since 1.2
98       */
99      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 }