View Javadoc
1   /*
2    *   Copyright (C) 2014 Christian Schulte <cs@schulte.it>
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 5353 2016-09-05 04:58:44Z schulte $
29   *
30   */
31  package org.jomc.modlet;
32  
33  import java.io.ByteArrayInputStream;
34  import java.io.ByteArrayOutputStream;
35  import java.io.IOException;
36  import java.lang.reflect.UndeclaredThrowableException;
37  import java.net.URISyntaxException;
38  import java.net.URL;
39  import java.text.MessageFormat;
40  import java.util.Enumeration;
41  import java.util.LinkedList;
42  import java.util.List;
43  import java.util.Locale;
44  import java.util.Map;
45  import java.util.Properties;
46  import java.util.ResourceBundle;
47  import java.util.concurrent.Callable;
48  import java.util.concurrent.CancellationException;
49  import java.util.concurrent.ExecutionException;
50  import java.util.concurrent.Future;
51  import java.util.logging.Level;
52  import javax.xml.bind.JAXBContext;
53  import javax.xml.bind.JAXBElement;
54  import javax.xml.bind.JAXBException;
55  import javax.xml.bind.util.JAXBResult;
56  import javax.xml.bind.util.JAXBSource;
57  import javax.xml.transform.ErrorListener;
58  import javax.xml.transform.Transformer;
59  import javax.xml.transform.TransformerConfigurationException;
60  import javax.xml.transform.TransformerException;
61  import javax.xml.transform.TransformerFactory;
62  import javax.xml.transform.stream.StreamSource;
63  
64  /**
65   * Default {@code ModletProcessor} implementation.
66   *
67   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
68   * @version $JOMC: DefaultModletProcessor.java 5353 2016-09-05 04:58:44Z schulte $
69   * @see ModelContext#processModlets(org.jomc.modlet.Modlets)
70   * @since 1.6
71   */
72  public class DefaultModletProcessor implements ModletProcessor
73  {
74  
75      /**
76       * Constant for the name of the model context attribute backing property {@code enabled}.
77       *
78       * @see #processModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
79       * @see ModelContext#getAttribute(java.lang.String)
80       */
81      public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.modlet.DefaultModletProcessor.enabledAttribute";
82  
83      /**
84       * Constant for the name of the system property controlling property {@code defaultEnabled}.
85       *
86       * @see #isDefaultEnabled()
87       */
88      private static final String DEFAULT_ENABLED_PROPERTY_NAME =
89          "org.jomc.modlet.DefaultModletProcessor.defaultEnabled";
90  
91      /**
92       * Default value of the flag indicating the processor is enabled by default.
93       *
94       * @see #isDefaultEnabled()
95       */
96      private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
97  
98      /**
99       * Flag indicating the processor is enabled by default.
100      */
101     private static volatile Boolean defaultEnabled;
102 
103     /**
104      * Flag indicating the processor is enabled.
105      */
106     private volatile Boolean enabled;
107 
108     /**
109      * Constant for the name of the system property controlling property {@code defaultOrdinal}.
110      *
111      * @see #getDefaultOrdinal()
112      */
113     private static final String DEFAULT_ORDINAL_PROPERTY_NAME =
114         "org.jomc.modlet.DefaultModletProcessor.defaultOrdinal";
115 
116     /**
117      * Default value of the ordinal number of the processor.
118      *
119      * @see #getDefaultOrdinal()
120      */
121     private static final Integer DEFAULT_ORDINAL = 0;
122 
123     /**
124      * Default ordinal number of the processor.
125      */
126     private static volatile Integer defaultOrdinal;
127 
128     /**
129      * Ordinal number of the processor.
130      */
131     private volatile Integer ordinal;
132 
133     /**
134      * Constant for the name of the model context attribute backing property {@code transformerLocation}.
135      *
136      * @see #processModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
137      * @see ModelContext#getAttribute(java.lang.String)
138      * @since 1.2
139      */
140     public static final String TRANSFORMER_LOCATION_ATTRIBUTE_NAME =
141         "org.jomc.modlet.DefaultModletProcessor.transformerLocationAttribute";
142 
143     /**
144      * Constant for the name of the system property controlling property {@code defaultTransformerLocation}.
145      *
146      * @see #getDefaultTransformerLocation()
147      */
148     private static final String DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME =
149         "org.jomc.modlet.DefaultModletProcessor.defaultTransformerLocation";
150 
151     /**
152      * Class path location searched for transformers by default.
153      *
154      * @see #getDefaultTransformerLocation()
155      */
156     private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc-modlet.xsl";
157 
158     /**
159      * Default transformer location.
160      */
161     private static volatile String defaultTransformerLocation;
162 
163     /**
164      * Transformer location of the instance.
165      */
166     private volatile String transformerLocation;
167 
168     /**
169      * Creates a new {@code DefaultModletProcessor} instance.
170      */
171     public DefaultModletProcessor()
172     {
173         super();
174     }
175 
176     /**
177      * Gets a flag indicating the processor is enabled by default.
178      * <p>
179      * The default enabled flag is controlled by system property
180      * {@code org.jomc.modlet.DefaultModletProcessor.defaultEnabled} holding a value indicating the processor is
181      * enabled by default. If that property is not set, the {@code true} default is returned.
182      * </p>
183      *
184      * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by
185      * default.
186      *
187      * @see #isEnabled()
188      * @see #setDefaultEnabled(java.lang.Boolean)
189      */
190     public static boolean isDefaultEnabled()
191     {
192         if ( defaultEnabled == null )
193         {
194             defaultEnabled = Boolean.valueOf( System.getProperty(
195                 DEFAULT_ENABLED_PROPERTY_NAME, Boolean.toString( DEFAULT_ENABLED ) ) );
196 
197         }
198 
199         return defaultEnabled;
200     }
201 
202     /**
203      * Sets the flag indicating the processor is enabled by default.
204      *
205      * @param value The new value of the flag indicating the processor is enabled by default or {@code null}.
206      *
207      * @see #isDefaultEnabled()
208      */
209     public static void setDefaultEnabled( final Boolean value )
210     {
211         defaultEnabled = value;
212     }
213 
214     /**
215      * Gets a flag indicating the processor is enabled.
216      *
217      * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled.
218      *
219      * @see #isDefaultEnabled()
220      * @see #setEnabled(java.lang.Boolean)
221      */
222     public final boolean isEnabled()
223     {
224         if ( this.enabled == null )
225         {
226             this.enabled = isDefaultEnabled();
227         }
228 
229         return this.enabled;
230     }
231 
232     /**
233      * Sets the flag indicating the processor is enabled.
234      *
235      * @param value The new value of the flag indicating the processor is enabled or {@code null}.
236      *
237      * @see #isEnabled()
238      */
239     public final void setEnabled( final Boolean value )
240     {
241         this.enabled = value;
242     }
243 
244     /**
245      * Gets the default ordinal number of the processor.
246      * <p>
247      * The default ordinal number is controlled by system property
248      * {@code org.jomc.modlet.DefaultModletProvider.defaultOrdinal} holding the default ordinal number of the processor.
249      * If that property is not set, the {@code 0} default is returned.
250      * </p>
251      *
252      * @return The default ordinal number of the processor.
253      *
254      * @see #setDefaultOrdinal(java.lang.Integer)
255      */
256     public static int getDefaultOrdinal()
257     {
258         if ( defaultOrdinal == null )
259         {
260             defaultOrdinal = Integer.getInteger( DEFAULT_ORDINAL_PROPERTY_NAME, DEFAULT_ORDINAL );
261         }
262 
263         return defaultOrdinal;
264     }
265 
266     /**
267      * Sets the default ordinal number of the processor.
268      *
269      * @param value The new default ordinal number of the processor or {@code null}.
270      *
271      * @see #getDefaultOrdinal()
272      */
273     public static void setDefaultOrdinal( final Integer value )
274     {
275         defaultOrdinal = value;
276     }
277 
278     /**
279      * Gets the ordinal number of the processor.
280      *
281      * @return The ordinal number of the processor.
282      *
283      * @see #getDefaultOrdinal()
284      * @see #setOrdinal(java.lang.Integer)
285      */
286     public final int getOrdinal()
287     {
288         if ( this.ordinal == null )
289         {
290             this.ordinal = getDefaultOrdinal();
291         }
292 
293         return this.ordinal;
294     }
295 
296     /**
297      * Sets the ordinal number of the processor.
298      *
299      * @param value The new ordinal number of the processor or {@code null}.
300      *
301      * @see #getOrdinal()
302      */
303     public final void setOrdinal( final Integer value )
304     {
305         this.ordinal = value;
306     }
307 
308     /**
309      * Gets the default location searched for transformer resources.
310      * <p>
311      * The default transformer location is controlled by system property
312      * {@code org.jomc.modlet.DefaultModletProcessor.defaultTransformerLocation} holding the location to search
313      * for transformer resources by default. If that property is not set, the {@code META-INF/jomc-modlet.xsl} default
314      * is returned.
315      * </p>
316      *
317      * @return The location searched for transformer resources by default.
318      *
319      * @see #setDefaultTransformerLocation(java.lang.String)
320      */
321     public static String getDefaultTransformerLocation()
322     {
323         if ( defaultTransformerLocation == null )
324         {
325             defaultTransformerLocation =
326                 System.getProperty( DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME, DEFAULT_TRANSFORMER_LOCATION );
327 
328         }
329 
330         return defaultTransformerLocation;
331     }
332 
333     /**
334      * Sets the default location searched for transformer resources.
335      *
336      * @param value The new default location to search for transformer resources or {@code null}.
337      *
338      * @see #getDefaultTransformerLocation()
339      */
340     public static void setDefaultTransformerLocation( final String value )
341     {
342         defaultTransformerLocation = value;
343     }
344 
345     /**
346      * Gets the location searched for transformer resources.
347      *
348      * @return The location searched for transformer resources.
349      *
350      * @see #getDefaultTransformerLocation()
351      * @see #setTransformerLocation(java.lang.String)
352      */
353     public final String getTransformerLocation()
354     {
355         if ( this.transformerLocation == null )
356         {
357             this.transformerLocation = getDefaultTransformerLocation();
358         }
359 
360         return this.transformerLocation;
361     }
362 
363     /**
364      * Sets the location searched for transformer resources.
365      *
366      * @param value The new location to search for transformer resources or {@code null}.
367      *
368      * @see #getTransformerLocation()
369      */
370     public final void setTransformerLocation( final String value )
371     {
372         this.transformerLocation = value;
373     }
374 
375     /**
376      * Searches a given context for transformers.
377      *
378      * @param context The context to search for transformers.
379      * @param location The location to search at.
380      *
381      * @return The transformers found at {@code location} in {@code context} or {@code null}, if no transformers are
382      * found.
383      *
384      * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
385      * @throws ModelException if getting the transformers fails.
386      */
387     public List<Transformer> findTransformers( final ModelContext context, final String location ) throws ModelException
388     {
389         if ( context == null )
390         {
391             throw new NullPointerException( "context" );
392         }
393         if ( location == null )
394         {
395             throw new NullPointerException( "location" );
396         }
397 
398         try
399         {
400             final long t0 = System.nanoTime();
401             final List<Transformer> transformers = new LinkedList<Transformer>();
402             final Enumeration<URL> transformerResourceEnumeration = context.findResources( location );
403             final ErrorListener errorListener = new ErrorListener()
404             {
405 
406                 public void warning( final TransformerException exception ) throws TransformerException
407                 {
408                     if ( context.isLoggable( Level.WARNING ) )
409                     {
410                         context.log( Level.WARNING, getMessage( exception ), exception );
411                     }
412                 }
413 
414                 public void error( final TransformerException exception ) throws TransformerException
415                 {
416                     if ( context.isLoggable( Level.SEVERE ) )
417                     {
418                         context.log( Level.SEVERE, getMessage( exception ), exception );
419                     }
420 
421                     throw exception;
422                 }
423 
424                 public void fatalError( final TransformerException exception ) throws TransformerException
425                 {
426                     if ( context.isLoggable( Level.SEVERE ) )
427                     {
428                         context.log( Level.SEVERE, getMessage( exception ), exception );
429                     }
430 
431                     throw exception;
432                 }
433 
434             };
435 
436             final Properties parameters = getTransformerParameters();
437             final ThreadLocal<TransformerFactory> threadLocalTransformerFactory = new ThreadLocal<TransformerFactory>();
438 
439             class CreateTansformerTask implements Callable<Transformer>
440             {
441 
442                 private final URL resource;
443 
444                 CreateTansformerTask( final URL resource )
445                 {
446                     super();
447                     this.resource = resource;
448                 }
449 
450                 public Transformer call() throws ModelException
451                 {
452                     try
453                     {
454                         TransformerFactory transformerFactory = threadLocalTransformerFactory.get();
455                         if ( transformerFactory == null )
456                         {
457                             transformerFactory = TransformerFactory.newInstance();
458                             transformerFactory.setErrorListener( errorListener );
459                             threadLocalTransformerFactory.set( transformerFactory );
460                         }
461 
462                         if ( context.isLoggable( Level.FINEST ) )
463                         {
464                             context.log( Level.FINEST, getMessage( "processing", this.resource.toExternalForm() ),
465                                          null );
466 
467                         }
468 
469                         final Transformer transformer = transformerFactory.newTransformer(
470                             new StreamSource( this.resource.toURI().toASCIIString() ) );
471 
472                         transformer.setErrorListener( errorListener );
473 
474                         for ( final Map.Entry<Object, Object> e : parameters.entrySet() )
475                         {
476                             transformer.setParameter( e.getKey().toString(), e.getValue() );
477                         }
478 
479                         return transformer;
480                     }
481                     catch ( final TransformerConfigurationException e )
482                     {
483                         String message = getMessage( e );
484                         if ( message == null && e.getException() != null )
485                         {
486                             message = getMessage( e.getException() );
487                         }
488 
489                         throw new ModelException( message, e );
490                     }
491                     catch ( final URISyntaxException e )
492                     {
493                         throw new ModelException( getMessage( e ), e );
494                     }
495                 }
496 
497             }
498 
499             final List<CreateTansformerTask> tasks = new LinkedList<CreateTansformerTask>();
500 
501             while ( transformerResourceEnumeration.hasMoreElements() )
502             {
503                 tasks.add( new CreateTansformerTask( transformerResourceEnumeration.nextElement() ) );
504             }
505 
506             if ( context.getExecutorService() != null && tasks.size() > 1 )
507             {
508                 for ( final Future<Transformer> task : context.getExecutorService().invokeAll( tasks ) )
509                 {
510                     transformers.add( task.get() );
511                 }
512             }
513             else
514             {
515                 for ( final CreateTansformerTask task : tasks )
516                 {
517                     transformers.add( task.call() );
518                 }
519             }
520 
521             if ( context.isLoggable( Level.FINE ) )
522             {
523                 context.log( Level.FINE, getMessage( "contextReport", tasks.size(), location, System.nanoTime() - t0 ),
524                              null );
525 
526             }
527 
528             return transformers.isEmpty() ? null : transformers;
529         }
530         catch ( final CancellationException e )
531         {
532             throw new ModelException( getMessage( e ), e );
533         }
534         catch ( final InterruptedException e )
535         {
536             throw new ModelException( getMessage( e ), e );
537         }
538         catch ( final ExecutionException e )
539         {
540             if ( e.getCause() instanceof ModelException )
541             {
542                 throw (ModelException) e.getCause();
543             }
544             else if ( e.getCause() instanceof RuntimeException )
545             {
546                 // The fork-join framework breaks the exception handling contract of Callable by re-throwing any
547                 // exception caught using a runtime exception.
548                 if ( e.getCause().getCause() instanceof ModelException )
549                 {
550                     throw (ModelException) e.getCause().getCause();
551                 }
552                 else if ( e.getCause().getCause() instanceof RuntimeException )
553                 {
554                     throw (RuntimeException) e.getCause().getCause();
555                 }
556                 else if ( e.getCause().getCause() instanceof Error )
557                 {
558                     throw (Error) e.getCause().getCause();
559                 }
560                 else if ( e.getCause().getCause() instanceof Exception )
561                 {
562                     // Checked exception not declared to be thrown by the Callable's 'call' method.
563                     throw new UndeclaredThrowableException( e.getCause().getCause() );
564                 }
565                 else
566                 {
567                     throw (RuntimeException) e.getCause();
568                 }
569             }
570             else if ( e.getCause() instanceof Error )
571             {
572                 throw (Error) e.getCause();
573             }
574             else
575             {
576                 // Checked exception not declared to be thrown by the Callable's 'call' method.
577                 throw new UndeclaredThrowableException( e.getCause() );
578             }
579         }
580     }
581 
582     /**
583      * {@inheritDoc}
584      *
585      * @see #isEnabled()
586      * @see #getTransformerLocation()
587      * @see #findTransformers(org.jomc.modlet.ModelContext, java.lang.String)
588      * @see #ENABLED_ATTRIBUTE_NAME
589      * @see #TRANSFORMER_LOCATION_ATTRIBUTE_NAME
590      */
591     public Modlets processModlets( final ModelContext context, final Modlets modlets ) throws ModelException
592     {
593         if ( context == null )
594         {
595             throw new NullPointerException( "context" );
596         }
597         if ( modlets == null )
598         {
599             throw new NullPointerException( "modlets" );
600         }
601 
602         try
603         {
604             Modlets processed = null;
605 
606             boolean contextEnabled = this.isEnabled();
607             if ( DEFAULT_ENABLED == contextEnabled
608                      && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
609             {
610                 contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
611             }
612 
613             String contextTransformerLocation = this.getTransformerLocation();
614             if ( DEFAULT_TRANSFORMER_LOCATION.equals( contextTransformerLocation )
615                      && context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
616             {
617                 contextTransformerLocation = (String) context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME );
618             }
619 
620             if ( contextEnabled )
621             {
622                 final org.jomc.modlet.ObjectFactory objectFactory = new org.jomc.modlet.ObjectFactory();
623                 final JAXBContext jaxbContext = context.createContext( ModletObject.MODEL_PUBLIC_ID );
624                 final List<Transformer> transformers = this.findTransformers( context, contextTransformerLocation );
625 
626                 if ( transformers != null )
627                 {
628                     processed = modlets.clone();
629 
630                     for ( int i = 0, s0 = transformers.size(); i < s0; i++ )
631                     {
632                         final JAXBElement<Modlets> e = objectFactory.createModlets( processed );
633                         final JAXBSource source = new JAXBSource( jaxbContext, e );
634                         final JAXBResult result = new JAXBResult( jaxbContext );
635                         transformers.get( i ).transform( source, result );
636 
637                         if ( result.getResult() instanceof JAXBElement<?>
638                                  && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Modlets )
639                         {
640                             processed = (Modlets) ( (JAXBElement<?>) result.getResult() ).getValue();
641                         }
642                         else
643                         {
644                             throw new ModelException( getMessage( "illegalTransformationResult" ) );
645                         }
646                     }
647                 }
648             }
649             else if ( context.isLoggable( Level.FINER ) )
650             {
651                 context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName() ), null );
652             }
653 
654             return processed;
655         }
656         catch ( final TransformerException e )
657         {
658             String message = getMessage( e );
659             if ( message == null && e.getException() != null )
660             {
661                 message = getMessage( e.getException() );
662             }
663 
664             throw new ModelException( message, e );
665         }
666         catch ( final JAXBException e )
667         {
668             String message = getMessage( e );
669             if ( message == null && e.getLinkedException() != null )
670             {
671                 message = getMessage( e.getLinkedException() );
672             }
673 
674             throw new ModelException( message, e );
675         }
676     }
677 
678     private static Properties getTransformerParameters() throws ModelException
679     {
680         final Properties properties = new Properties();
681 
682         ByteArrayInputStream in = null;
683         ByteArrayOutputStream out = null;
684         try
685         {
686             out = new ByteArrayOutputStream();
687             System.getProperties().store( out, DefaultModletProcessor.class.getName() );
688             out.close();
689             final byte[] bytes = out.toByteArray();
690             out = null;
691 
692             in = new ByteArrayInputStream( bytes );
693             properties.load( in );
694             in.close();
695             in = null;
696         }
697         catch ( final IOException e )
698         {
699             throw new ModelException( getMessage( e ), e );
700         }
701         finally
702         {
703             try
704             {
705                 if ( out != null )
706                 {
707                     out.close();
708                 }
709             }
710             catch ( final IOException e )
711             {
712                 // Suppressed.
713             }
714             finally
715             {
716                 try
717                 {
718                     if ( in != null )
719                     {
720                         in.close();
721                     }
722                 }
723                 catch ( final IOException e )
724                 {
725                     // Suppressed.
726                 }
727             }
728         }
729 
730         return properties;
731     }
732 
733     private static String getMessage( final String key, final Object... args )
734     {
735         return MessageFormat.format( ResourceBundle.getBundle( DefaultModletProcessor.class.getName(),
736                                                                Locale.getDefault() ).getString( key ), args );
737 
738     }
739 
740     private static String getMessage( final Throwable t )
741     {
742         return t != null
743                    ? t.getMessage() != null && t.getMessage().trim().length() > 0
744                          ? t.getMessage()
745                          : getMessage( t.getCause() )
746                    : null;
747 
748     }
749 
750 }