001 /*
002 * Copyright (C) Christian Schulte, 2005-206
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions
007 * are met:
008 *
009 * o Redistributions of source code must retain the above copyright
010 * notice, this list of conditions and the following disclaimer.
011 *
012 * o Redistributions in binary form must reproduce the above copyright
013 * notice, this list of conditions and the following disclaimer in
014 * the documentation and/or other materials provided with the
015 * distribution.
016 *
017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 * $JOMC: DefaultModelProcessor.java 4201 2012-01-25 09:47:12Z schulte2005 $
029 *
030 */
031 package org.jomc.model.modlet;
032
033 import java.net.URISyntaxException;
034 import java.net.URL;
035 import java.text.MessageFormat;
036 import java.util.Enumeration;
037 import java.util.LinkedList;
038 import java.util.List;
039 import java.util.Locale;
040 import java.util.Map;
041 import java.util.ResourceBundle;
042 import java.util.logging.Level;
043 import javax.xml.bind.JAXBContext;
044 import javax.xml.bind.JAXBElement;
045 import javax.xml.bind.JAXBException;
046 import javax.xml.bind.util.JAXBResult;
047 import javax.xml.bind.util.JAXBSource;
048 import javax.xml.transform.ErrorListener;
049 import javax.xml.transform.Transformer;
050 import javax.xml.transform.TransformerConfigurationException;
051 import javax.xml.transform.TransformerException;
052 import javax.xml.transform.TransformerFactory;
053 import javax.xml.transform.stream.StreamSource;
054 import org.jomc.modlet.Model;
055 import org.jomc.modlet.ModelContext;
056 import org.jomc.modlet.ModelException;
057 import org.jomc.modlet.ModelProcessor;
058
059 /**
060 * Default object management and configuration {@code ModelProcessor} implementation.
061 *
062 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
063 * @version $JOMC: DefaultModelProcessor.java 4201 2012-01-25 09:47:12Z schulte2005 $
064 * @see ModelContext#processModel(org.jomc.modlet.Model)
065 */
066 public class DefaultModelProcessor implements ModelProcessor
067 {
068
069 /**
070 * Constant for the name of the model context attribute backing property {@code enabled}.
071 * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
072 * @see ModelContext#getAttribute(java.lang.String)
073 * @since 1.2
074 */
075 public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProcessor.enabledAttribute";
076
077 /**
078 * Constant for the name of the system property controlling property {@code defaultEnabled}.
079 * @see #isDefaultEnabled()
080 */
081 private static final String DEFAULT_ENABLED_PROPERTY_NAME =
082 "org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled";
083
084 /**
085 * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}.
086 * @see #isDefaultEnabled()
087 */
088 private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME =
089 "org.jomc.model.DefaultModelProcessor.defaultEnabled";
090
091 /**
092 * Default value of the flag indicating the processor is enabled by default.
093 * @see #isDefaultEnabled()
094 * @since 1.2
095 */
096 private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
097
098 /** Flag indicating the processor is enabled by default. */
099 private static volatile Boolean defaultEnabled;
100
101 /** Flag indicating the processor is enabled. */
102 private Boolean enabled;
103
104 /**
105 * Constant for the name of the model context attribute backing property {@code transformerLocation}.
106 * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
107 * @see ModelContext#getAttribute(java.lang.String)
108 * @since 1.2
109 */
110 public static final String TRANSFORMER_LOCATION_ATTRIBUTE_NAME =
111 "org.jomc.model.modlet.DefaultModelProcessor.transformerLocationAttribute";
112
113 /**
114 * Constant for the name of the system property controlling property {@code defaultTransformerLocation}.
115 * @see #getDefaultTransformerLocation()
116 */
117 private static final String DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME =
118 "org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation";
119
120 /**
121 * Constant for the name of the deprecated system property controlling property {@code defaultTransformerLocation}.
122 * @see #getDefaultTransformerLocation()
123 */
124 private static final String DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME =
125 "org.jomc.model.DefaultModelProcessor.defaultTransformerLocation";
126
127 /**
128 * Class path location searched for transformers by default.
129 * @see #getDefaultTransformerLocation()
130 */
131 private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc.xsl";
132
133 /** Default transformer location. */
134 private static volatile String defaultTransformerLocation;
135
136 /** Transformer location of the instance. */
137 private String transformerLocation;
138
139 /** Creates a new {@code DefaultModelProcessor} instance. */
140 public DefaultModelProcessor()
141 {
142 super();
143 }
144
145 /**
146 * Gets a flag indicating the processor is enabled by default.
147 * <p>The default enabled flag is controlled by system property
148 * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled} holding a value indicating the processor is
149 * enabled by default. If that property is not set, the {@code true} default is returned.</p>
150 *
151 * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by
152 * default.
153 *
154 * @see #setDefaultEnabled(java.lang.Boolean)
155 */
156 public static boolean isDefaultEnabled()
157 {
158 if ( defaultEnabled == null )
159 {
160 defaultEnabled =
161 Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME,
162 System.getProperty( DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME,
163 Boolean.toString( DEFAULT_ENABLED ) ) ) );
164
165 }
166
167 return defaultEnabled;
168 }
169
170 /**
171 * Sets the flag indicating the processor is enabled by default.
172 *
173 * @param value The new value of the flag indicating the processor is enabled by default or {@code null}.
174 *
175 * @see #isDefaultEnabled()
176 */
177 public static void setDefaultEnabled( final Boolean value )
178 {
179 defaultEnabled = value;
180 }
181
182 /**
183 * Gets a flag indicating the processor is enabled.
184 *
185 * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled.
186 *
187 * @see #isDefaultEnabled()
188 * @see #setEnabled(java.lang.Boolean)
189 */
190 public final boolean isEnabled()
191 {
192 if ( this.enabled == null )
193 {
194 this.enabled = isDefaultEnabled();
195 }
196
197 return this.enabled;
198 }
199
200 /**
201 * Sets the flag indicating the processor is enabled.
202 *
203 * @param value The new value of the flag indicating the processor is enabled or {@code null}.
204 *
205 * @see #isEnabled()
206 */
207 public final void setEnabled( final Boolean value )
208 {
209 this.enabled = value;
210 }
211
212 /**
213 * Gets the default location searched for transformer resources.
214 * <p>The default transformer location is controlled by system property
215 * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation} holding the location to search for
216 * transformer resources by default. If that property is not set, the {@code META-INF/jomc.xsl} default is
217 * returned.</p>
218 *
219 * @return The location searched for transformer resources by default.
220 *
221 * @see #setDefaultTransformerLocation(java.lang.String)
222 */
223 public static String getDefaultTransformerLocation()
224 {
225 if ( defaultTransformerLocation == null )
226 {
227 defaultTransformerLocation =
228 System.getProperty( DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME,
229 System.getProperty( DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME,
230 DEFAULT_TRANSFORMER_LOCATION ) );
231
232 }
233
234 return defaultTransformerLocation;
235 }
236
237 /**
238 * Sets the default location searched for transformer resources.
239 *
240 * @param value The new default location to search for transformer resources or {@code null}.
241 *
242 * @see #getDefaultTransformerLocation()
243 */
244 public static void setDefaultTransformerLocation( final String value )
245 {
246 defaultTransformerLocation = value;
247 }
248
249 /**
250 * Gets the location searched for transformer resources.
251 *
252 * @return The location searched for transformer resources.
253 *
254 * @see #getDefaultTransformerLocation()
255 * @see #setTransformerLocation(java.lang.String)
256 */
257 public final String getTransformerLocation()
258 {
259 if ( this.transformerLocation == null )
260 {
261 this.transformerLocation = getDefaultTransformerLocation();
262 }
263
264 return this.transformerLocation;
265 }
266
267 /**
268 * Sets the location searched for transformer resources.
269 *
270 * @param value The new location to search for transformer resources or {@code null}.
271 *
272 * @see #getTransformerLocation()
273 */
274 public final void setTransformerLocation( final String value )
275 {
276 this.transformerLocation = value;
277 }
278
279 /**
280 * Searches a given context for transformers.
281 *
282 * @param context The context to search for transformers.
283 * @param location The location to search at.
284 *
285 * @return The transformers found at {@code location} in {@code context} or {@code null}, if no transformers are
286 * found.
287 *
288 * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
289 * @throws ModelException if getting the transformers fails.
290 */
291 public List<Transformer> findTransformers( final ModelContext context, final String location ) throws ModelException
292 {
293 if ( context == null )
294 {
295 throw new NullPointerException( "context" );
296 }
297 if ( location == null )
298 {
299 throw new NullPointerException( "location" );
300 }
301
302 try
303 {
304 final long t0 = System.currentTimeMillis();
305 final List<Transformer> transformers = new LinkedList<Transformer>();
306 final TransformerFactory transformerFactory = TransformerFactory.newInstance();
307 final Enumeration<URL> resources = context.findResources( location );
308 final ErrorListener errorListener = new ErrorListener()
309 {
310
311 public void warning( final TransformerException exception ) throws TransformerException
312 {
313 if ( context.isLoggable( Level.WARNING ) )
314 {
315 context.log( Level.WARNING, getMessage( exception ), exception );
316 }
317 }
318
319 public void error( final TransformerException exception ) throws TransformerException
320 {
321 if ( context.isLoggable( Level.SEVERE ) )
322 {
323 context.log( Level.SEVERE, getMessage( exception ), exception );
324 }
325
326 throw exception;
327 }
328
329 public void fatalError( final TransformerException exception ) throws TransformerException
330 {
331 if ( context.isLoggable( Level.SEVERE ) )
332 {
333 context.log( Level.SEVERE, getMessage( exception ), exception );
334 }
335
336 throw exception;
337 }
338
339 };
340
341 transformerFactory.setErrorListener( errorListener );
342
343 int count = 0;
344 while ( resources.hasMoreElements() )
345 {
346 count++;
347 final URL url = resources.nextElement();
348
349 if ( context.isLoggable( Level.FINEST ) )
350 {
351 context.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null );
352 }
353
354 final Transformer transformer =
355 transformerFactory.newTransformer( new StreamSource( url.toURI().toASCIIString() ) );
356
357 transformer.setErrorListener( errorListener );
358
359 for ( Map.Entry<Object, Object> e : System.getProperties().entrySet() )
360 {
361 transformer.setParameter( e.getKey().toString(), e.getValue() );
362 }
363
364 transformers.add( transformer );
365 }
366
367 if ( context.isLoggable( Level.FINE ) )
368 {
369 context.log( Level.FINE, getMessage( "contextReport", count, location,
370 Long.valueOf( System.currentTimeMillis() - t0 ) ), null );
371
372 }
373
374 return transformers.isEmpty() ? null : transformers;
375 }
376 catch ( final URISyntaxException e )
377 {
378 throw new ModelException( getMessage( e ), e );
379 }
380 catch ( final TransformerConfigurationException e )
381 {
382 String message = getMessage( e );
383 if ( message == null && e.getException() != null )
384 {
385 message = getMessage( e.getException() );
386 }
387
388 throw new ModelException( message, e );
389 }
390 }
391
392 /**
393 * {@inheritDoc}
394 *
395 * @see #isEnabled()
396 * @see #getTransformerLocation()
397 * @see #findTransformers(org.jomc.modlet.ModelContext, java.lang.String)
398 * @see #ENABLED_ATTRIBUTE_NAME
399 * @see #TRANSFORMER_LOCATION_ATTRIBUTE_NAME
400 */
401 public Model processModel( final ModelContext context, final Model model ) throws ModelException
402 {
403 if ( context == null )
404 {
405 throw new NullPointerException( "context" );
406 }
407 if ( model == null )
408 {
409 throw new NullPointerException( "model" );
410 }
411
412 try
413 {
414 Model processed = model;
415
416 boolean contextEnabled = this.isEnabled();
417 if ( DEFAULT_ENABLED == contextEnabled
418 && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
419 {
420 contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
421 }
422
423 String contextTransformerLocation = this.getTransformerLocation();
424 if ( DEFAULT_TRANSFORMER_LOCATION.equals( contextTransformerLocation )
425 && context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
426 {
427 contextTransformerLocation = (String) context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME );
428 }
429
430 if ( contextEnabled )
431 {
432 final org.jomc.modlet.ObjectFactory objectFactory = new org.jomc.modlet.ObjectFactory();
433 final JAXBContext jaxbContext = context.createContext( model.getIdentifier() );
434 final List<Transformer> transformers = this.findTransformers( context, contextTransformerLocation );
435 processed = model.clone();
436
437 if ( transformers != null )
438 {
439 for ( int i = 0, s0 = transformers.size(); i < s0; i++ )
440 {
441 final JAXBElement<Model> e = objectFactory.createModel( processed );
442 final JAXBSource source = new JAXBSource( jaxbContext, e );
443 final JAXBResult result = new JAXBResult( jaxbContext );
444 transformers.get( i ).transform( source, result );
445
446 if ( result.getResult() instanceof JAXBElement<?>
447 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Model )
448 {
449 processed = (Model) ( (JAXBElement<?>) result.getResult() ).getValue();
450 }
451 else
452 {
453 throw new ModelException( getMessage(
454 "illegalTransformationResult", model.getIdentifier() ) );
455
456 }
457 }
458 }
459 }
460 else if ( context.isLoggable( Level.FINER ) )
461 {
462 context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(),
463 model.getIdentifier() ), null );
464
465 }
466
467 return processed;
468 }
469 catch ( final TransformerException e )
470 {
471 String message = getMessage( e );
472 if ( message == null && e.getException() != null )
473 {
474 message = getMessage( e.getException() );
475 }
476
477 throw new ModelException( message, e );
478 }
479 catch ( final JAXBException e )
480 {
481 String message = getMessage( e );
482 if ( message == null && e.getLinkedException() != null )
483 {
484 message = getMessage( e.getLinkedException() );
485 }
486
487 throw new ModelException( message, e );
488 }
489 }
490
491 private static String getMessage( final String key, final Object... args )
492 {
493 return MessageFormat.format( ResourceBundle.getBundle(
494 DefaultModelProcessor.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
495
496 }
497
498 private static String getMessage( final Throwable t )
499 {
500 return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
501 }
502
503 }