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 4760 2013-04-08 17:56:26Z schulte $ 029 * 030 */ 031package org.jomc.model.modlet; 032 033import java.net.URISyntaxException; 034import java.net.URL; 035import java.text.MessageFormat; 036import java.util.Enumeration; 037import java.util.LinkedList; 038import java.util.List; 039import java.util.Locale; 040import java.util.Map; 041import java.util.ResourceBundle; 042import java.util.logging.Level; 043import javax.xml.bind.JAXBContext; 044import javax.xml.bind.JAXBElement; 045import javax.xml.bind.JAXBException; 046import javax.xml.bind.util.JAXBResult; 047import javax.xml.bind.util.JAXBSource; 048import javax.xml.transform.ErrorListener; 049import javax.xml.transform.Transformer; 050import javax.xml.transform.TransformerConfigurationException; 051import javax.xml.transform.TransformerException; 052import javax.xml.transform.TransformerFactory; 053import javax.xml.transform.stream.StreamSource; 054import org.jomc.modlet.Model; 055import org.jomc.modlet.ModelContext; 056import org.jomc.modlet.ModelException; 057import org.jomc.modlet.ModelProcessor; 058 059/** 060 * Default object management and configuration {@code ModelProcessor} implementation. 061 * 062 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 063 * @version $JOMC: DefaultModelProcessor.java 4760 2013-04-08 17:56:26Z schulte $ 064 * @see ModelContext#processModel(org.jomc.modlet.Model) 065 */ 066public 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 501 ? t.getMessage() != null && t.getMessage().trim().length() > 0 502 ? t.getMessage() 503 : getMessage( t.getCause() ) 504 : null; 505 506 } 507 508}