View Javadoc
1   /*
2    *   Copyright (C) Christian Schulte <cs@schulte.it>, 2014-005
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: DefaultModletProcessor.java 5043 2015-05-27 07:03:39Z schulte $
29   *
30   */
31  package org.jomc.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  
55  /**
56   * Default {@code ModletProcessor} implementation.
57   *
58   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
59   * @version $JOMC: DefaultModletProcessor.java 5043 2015-05-27 07:03:39Z schulte $
60   * @see ModelContext#processModlets(org.jomc.modlet.Modlets)
61   * @since 1.6
62   */
63  public class DefaultModletProcessor implements ModletProcessor
64  {
65  
66      /**
67       * Constant for the name of the model context attribute backing property {@code enabled}.
68       *
69       * @see #processModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
70       * @see ModelContext#getAttribute(java.lang.String)
71       */
72      public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.modlet.DefaultModletProcessor.enabledAttribute";
73  
74      /**
75       * Constant for the name of the system property controlling property {@code defaultEnabled}.
76       *
77       * @see #isDefaultEnabled()
78       */
79      private static final String DEFAULT_ENABLED_PROPERTY_NAME =
80          "org.jomc.modlet.DefaultModletProcessor.defaultEnabled";
81  
82      /**
83       * Default value of the flag indicating the processor is enabled by default.
84       *
85       * @see #isDefaultEnabled()
86       */
87      private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
88  
89      /**
90       * Flag indicating the processor is enabled by default.
91       */
92      private static volatile Boolean defaultEnabled;
93  
94      /**
95       * Flag indicating the processor is enabled.
96       */
97      private Boolean enabled;
98  
99      /**
100      * Constant for the name of the system property controlling property {@code defaultOrdinal}.
101      *
102      * @see #getDefaultOrdinal()
103      */
104     private static final String DEFAULT_ORDINAL_PROPERTY_NAME =
105         "org.jomc.modlet.DefaultModletProcessor.defaultOrdinal";
106 
107     /**
108      * Default value of the ordinal number of the processor.
109      *
110      * @see #getDefaultOrdinal()
111      */
112     private static final Integer DEFAULT_ORDINAL = 0;
113 
114     /**
115      * Default ordinal number of the processor.
116      */
117     private static volatile Integer defaultOrdinal;
118 
119     /**
120      * Ordinal number of the processor.
121      */
122     private Integer ordinal;
123 
124     /**
125      * Constant for the name of the model context attribute backing property {@code transformerLocation}.
126      *
127      * @see #processModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
128      * @see ModelContext#getAttribute(java.lang.String)
129      * @since 1.2
130      */
131     public static final String TRANSFORMER_LOCATION_ATTRIBUTE_NAME =
132         "org.jomc.modlet.DefaultModletProcessor.transformerLocationAttribute";
133 
134     /**
135      * Constant for the name of the system property controlling property {@code defaultTransformerLocation}.
136      *
137      * @see #getDefaultTransformerLocation()
138      */
139     private static final String DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME =
140         "org.jomc.modlet.DefaultModletProcessor.defaultTransformerLocation";
141 
142     /**
143      * Class path location searched for transformers by default.
144      *
145      * @see #getDefaultTransformerLocation()
146      */
147     private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc-modlet.xsl";
148 
149     /**
150      * Default transformer location.
151      */
152     private static volatile String defaultTransformerLocation;
153 
154     /**
155      * Transformer location of the instance.
156      */
157     private String transformerLocation;
158 
159     /**
160      * Creates a new {@code DefaultModletProcessor} instance.
161      */
162     public DefaultModletProcessor()
163     {
164         super();
165     }
166 
167     /**
168      * Gets a flag indicating the processor is enabled by default.
169      * <p>
170      * The default enabled flag is controlled by system property
171      * {@code org.jomc.modlet.DefaultModletProcessor.defaultEnabled} holding a value indicating the processor is
172      * enabled by default. If that property is not set, the {@code true} default is returned.
173      * </p>
174      *
175      * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by
176      * default.
177      *
178      * @see #isEnabled()
179      * @see #setDefaultEnabled(java.lang.Boolean)
180      */
181     public static boolean isDefaultEnabled()
182     {
183         if ( defaultEnabled == null )
184         {
185             defaultEnabled = Boolean.valueOf( System.getProperty(
186                 DEFAULT_ENABLED_PROPERTY_NAME, Boolean.toString( DEFAULT_ENABLED ) ) );
187 
188         }
189 
190         return defaultEnabled;
191     }
192 
193     /**
194      * Sets the flag indicating the processor is enabled by default.
195      *
196      * @param value The new value of the flag indicating the processor is enabled by default or {@code null}.
197      *
198      * @see #isDefaultEnabled()
199      */
200     public static void setDefaultEnabled( final Boolean value )
201     {
202         defaultEnabled = value;
203     }
204 
205     /**
206      * Gets a flag indicating the processor is enabled.
207      *
208      * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled.
209      *
210      * @see #isDefaultEnabled()
211      * @see #setEnabled(java.lang.Boolean)
212      */
213     public final boolean isEnabled()
214     {
215         if ( this.enabled == null )
216         {
217             this.enabled = isDefaultEnabled();
218         }
219 
220         return this.enabled;
221     }
222 
223     /**
224      * Sets the flag indicating the processor is enabled.
225      *
226      * @param value The new value of the flag indicating the processor is enabled or {@code null}.
227      *
228      * @see #isEnabled()
229      */
230     public final void setEnabled( final Boolean value )
231     {
232         this.enabled = value;
233     }
234 
235     /**
236      * Gets the default ordinal number of the processor.
237      * <p>
238      * The default ordinal number is controlled by system property
239      * {@code org.jomc.modlet.DefaultModletProvider.defaultOrdinal} holding the default ordinal number of the processor.
240      * If that property is not set, the {@code 0} default is returned.
241      * </p>
242      *
243      * @return The default ordinal number of the processor.
244      *
245      * @see #setDefaultOrdinal(java.lang.Integer)
246      */
247     public static int getDefaultOrdinal()
248     {
249         if ( defaultOrdinal == null )
250         {
251             defaultOrdinal = Integer.getInteger( DEFAULT_ORDINAL_PROPERTY_NAME, DEFAULT_ORDINAL );
252         }
253 
254         return defaultOrdinal;
255     }
256 
257     /**
258      * Sets the default ordinal number of the processor.
259      *
260      * @param value The new default ordinal number of the processor or {@code null}.
261      *
262      * @see #getDefaultOrdinal()
263      */
264     public static void setDefaultOrdinal( final Integer value )
265     {
266         defaultOrdinal = value;
267     }
268 
269     /**
270      * Gets the ordinal number of the processor.
271      *
272      * @return The ordinal number of the processor.
273      *
274      * @see #getDefaultOrdinal()
275      * @see #setOrdinal(java.lang.Integer)
276      */
277     public final int getOrdinal()
278     {
279         if ( this.ordinal == null )
280         {
281             this.ordinal = getDefaultOrdinal();
282         }
283 
284         return this.ordinal;
285     }
286 
287     /**
288      * Sets the ordinal number of the processor.
289      *
290      * @param value The new ordinal number of the processor or {@code null}.
291      *
292      * @see #getOrdinal()
293      */
294     public final void setOrdinal( final Integer value )
295     {
296         this.ordinal = value;
297     }
298 
299     /**
300      * Gets the default location searched for transformer resources.
301      * <p>
302      * The default transformer location is controlled by system property
303      * {@code org.jomc.modlet.DefaultModletProcessor.defaultTransformerLocation} holding the location to search
304      * for transformer resources by default. If that property is not set, the {@code META-INF/jomc-modlet.xsl} default
305      * is returned.
306      * </p>
307      *
308      * @return The location searched for transformer resources by default.
309      *
310      * @see #setDefaultTransformerLocation(java.lang.String)
311      */
312     public static String getDefaultTransformerLocation()
313     {
314         if ( defaultTransformerLocation == null )
315         {
316             defaultTransformerLocation =
317                 System.getProperty( DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME, DEFAULT_TRANSFORMER_LOCATION );
318 
319         }
320 
321         return defaultTransformerLocation;
322     }
323 
324     /**
325      * Sets the default location searched for transformer resources.
326      *
327      * @param value The new default location to search for transformer resources or {@code null}.
328      *
329      * @see #getDefaultTransformerLocation()
330      */
331     public static void setDefaultTransformerLocation( final String value )
332     {
333         defaultTransformerLocation = value;
334     }
335 
336     /**
337      * Gets the location searched for transformer resources.
338      *
339      * @return The location searched for transformer resources.
340      *
341      * @see #getDefaultTransformerLocation()
342      * @see #setTransformerLocation(java.lang.String)
343      */
344     public final String getTransformerLocation()
345     {
346         if ( this.transformerLocation == null )
347         {
348             this.transformerLocation = getDefaultTransformerLocation();
349         }
350 
351         return this.transformerLocation;
352     }
353 
354     /**
355      * Sets the location searched for transformer resources.
356      *
357      * @param value The new location to search for transformer resources or {@code null}.
358      *
359      * @see #getTransformerLocation()
360      */
361     public final void setTransformerLocation( final String value )
362     {
363         this.transformerLocation = value;
364     }
365 
366     /**
367      * Searches a given context for transformers.
368      *
369      * @param context The context to search for transformers.
370      * @param location The location to search at.
371      *
372      * @return The transformers found at {@code location} in {@code context} or {@code null}, if no transformers are
373      * found.
374      *
375      * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
376      * @throws ModelException if getting the transformers fails.
377      */
378     public List<Transformer> findTransformers( final ModelContext context, final String location ) throws ModelException
379     {
380         if ( context == null )
381         {
382             throw new NullPointerException( "context" );
383         }
384         if ( location == null )
385         {
386             throw new NullPointerException( "location" );
387         }
388 
389         try
390         {
391             final long t0 = System.currentTimeMillis();
392             final List<Transformer> transformers = new LinkedList<Transformer>();
393             final TransformerFactory transformerFactory = TransformerFactory.newInstance();
394             final Enumeration<URL> resources = context.findResources( location );
395             final ErrorListener errorListener = new ErrorListener()
396             {
397 
398                 public void warning( final TransformerException exception ) throws TransformerException
399                 {
400                     if ( context.isLoggable( Level.WARNING ) )
401                     {
402                         context.log( Level.WARNING, getMessage( exception ), exception );
403                     }
404                 }
405 
406                 public void error( final TransformerException exception ) throws TransformerException
407                 {
408                     if ( context.isLoggable( Level.SEVERE ) )
409                     {
410                         context.log( Level.SEVERE, getMessage( exception ), exception );
411                     }
412 
413                     throw exception;
414                 }
415 
416                 public void fatalError( final TransformerException exception ) throws TransformerException
417                 {
418                     if ( context.isLoggable( Level.SEVERE ) )
419                     {
420                         context.log( Level.SEVERE, getMessage( exception ), exception );
421                     }
422 
423                     throw exception;
424                 }
425 
426             };
427 
428             transformerFactory.setErrorListener( errorListener );
429 
430             int count = 0;
431             while ( resources.hasMoreElements() )
432             {
433                 count++;
434                 final URL url = resources.nextElement();
435 
436                 if ( context.isLoggable( Level.FINEST ) )
437                 {
438                     context.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null );
439                 }
440 
441                 final Transformer transformer =
442                     transformerFactory.newTransformer( new StreamSource( url.toURI().toASCIIString() ) );
443 
444                 transformer.setErrorListener( errorListener );
445 
446                 for ( final Map.Entry<Object, Object> e : System.getProperties().entrySet() )
447                 {
448                     transformer.setParameter( e.getKey().toString(), e.getValue() );
449                 }
450 
451                 transformers.add( transformer );
452             }
453 
454             if ( context.isLoggable( Level.FINE ) )
455             {
456                 context.log( Level.FINE, getMessage( "contextReport", count, location,
457                                                      Long.valueOf( System.currentTimeMillis() - t0 ) ), null );
458 
459             }
460 
461             return transformers.isEmpty() ? null : transformers;
462         }
463         catch ( final URISyntaxException e )
464         {
465             throw new ModelException( getMessage( e ), e );
466         }
467         catch ( final TransformerConfigurationException e )
468         {
469             String message = getMessage( e );
470             if ( message == null && e.getException() != null )
471             {
472                 message = getMessage( e.getException() );
473             }
474 
475             throw new ModelException( message, e );
476         }
477     }
478 
479     /**
480      * {@inheritDoc}
481      *
482      * @see #isEnabled()
483      * @see #getTransformerLocation()
484      * @see #findTransformers(org.jomc.modlet.ModelContext, java.lang.String)
485      * @see #ENABLED_ATTRIBUTE_NAME
486      * @see #TRANSFORMER_LOCATION_ATTRIBUTE_NAME
487      */
488     public Modlets processModlets( final ModelContext context, final Modlets modlets ) throws ModelException
489     {
490         if ( context == null )
491         {
492             throw new NullPointerException( "context" );
493         }
494         if ( modlets == null )
495         {
496             throw new NullPointerException( "modlets" );
497         }
498 
499         try
500         {
501             Modlets processed = null;
502 
503             boolean contextEnabled = this.isEnabled();
504             if ( DEFAULT_ENABLED == contextEnabled
505                      && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
506             {
507                 contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
508             }
509 
510             String contextTransformerLocation = this.getTransformerLocation();
511             if ( DEFAULT_TRANSFORMER_LOCATION.equals( contextTransformerLocation )
512                      && context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
513             {
514                 contextTransformerLocation = (String) context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME );
515             }
516 
517             if ( contextEnabled )
518             {
519                 final org.jomc.modlet.ObjectFactory objectFactory = new org.jomc.modlet.ObjectFactory();
520                 final JAXBContext jaxbContext = context.createContext( ModletObject.MODEL_PUBLIC_ID );
521                 final List<Transformer> transformers = this.findTransformers( context, contextTransformerLocation );
522 
523                 if ( transformers != null )
524                 {
525                     processed = modlets.clone();
526 
527                     for ( int i = 0, s0 = transformers.size(); i < s0; i++ )
528                     {
529                         final JAXBElement<Modlets> e = objectFactory.createModlets( processed );
530                         final JAXBSource source = new JAXBSource( jaxbContext, e );
531                         final JAXBResult result = new JAXBResult( jaxbContext );
532                         transformers.get( i ).transform( source, result );
533 
534                         if ( result.getResult() instanceof JAXBElement<?>
535                                  && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Modlets )
536                         {
537                             processed = (Modlets) ( (JAXBElement<?>) result.getResult() ).getValue();
538                         }
539                         else
540                         {
541                             throw new ModelException( getMessage( "illegalTransformationResult" ) );
542                         }
543                     }
544                 }
545             }
546             else if ( context.isLoggable( Level.FINER ) )
547             {
548                 context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName() ), null );
549             }
550 
551             return processed;
552         }
553         catch ( final TransformerException e )
554         {
555             String message = getMessage( e );
556             if ( message == null && e.getException() != null )
557             {
558                 message = getMessage( e.getException() );
559             }
560 
561             throw new ModelException( message, e );
562         }
563         catch ( final JAXBException e )
564         {
565             String message = getMessage( e );
566             if ( message == null && e.getLinkedException() != null )
567             {
568                 message = getMessage( e.getLinkedException() );
569             }
570 
571             throw new ModelException( message, e );
572         }
573     }
574 
575     private static String getMessage( final String key, final Object... args )
576     {
577         return MessageFormat.format( ResourceBundle.getBundle(
578             DefaultModletProcessor.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
579 
580     }
581 
582     private static String getMessage( final Throwable t )
583     {
584         return t != null
585                    ? t.getMessage() != null && t.getMessage().trim().length() > 0
586                          ? t.getMessage()
587                          : getMessage( t.getCause() )
588                    : null;
589 
590     }
591 
592 }