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