001/* 002 * Copyright (C) Christian Schulte <cs@schulte.it>, 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 5043 2015-05-27 07:03:39Z 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 5043 2015-05-27 07:03:39Z 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 * 072 * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model) 073 * @see ModelContext#getAttribute(java.lang.String) 074 * @since 1.2 075 */ 076 public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProcessor.enabledAttribute"; 077 078 /** 079 * Constant for the name of the system property controlling property {@code defaultEnabled}. 080 * 081 * @see #isDefaultEnabled() 082 */ 083 private static final String DEFAULT_ENABLED_PROPERTY_NAME = 084 "org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled"; 085 086 /** 087 * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}. 088 * @see #isDefaultEnabled() 089 */ 090 private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME = 091 "org.jomc.model.DefaultModelProcessor.defaultEnabled"; 092 093 /** 094 * Default value of the flag indicating the processor is enabled by default. 095 * 096 * @see #isDefaultEnabled() 097 * @since 1.2 098 */ 099 private static final Boolean DEFAULT_ENABLED = Boolean.TRUE; 100 101 /** 102 * Flag indicating the processor is enabled by default. 103 */ 104 private static volatile Boolean defaultEnabled; 105 106 /** 107 * Flag indicating the processor is enabled. 108 */ 109 private Boolean enabled; 110 111 /** 112 * Constant for the name of the model context attribute backing property {@code transformerLocation}. 113 * 114 * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model) 115 * @see ModelContext#getAttribute(java.lang.String) 116 * @since 1.2 117 */ 118 public static final String TRANSFORMER_LOCATION_ATTRIBUTE_NAME = 119 "org.jomc.model.modlet.DefaultModelProcessor.transformerLocationAttribute"; 120 121 /** 122 * Constant for the name of the system property controlling property {@code defaultTransformerLocation}. 123 * 124 * @see #getDefaultTransformerLocation() 125 */ 126 private static final String DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME = 127 "org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation"; 128 129 /** 130 * Constant for the name of the deprecated system property controlling property {@code defaultTransformerLocation}. 131 * @see #getDefaultTransformerLocation() 132 */ 133 private static final String DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME = 134 "org.jomc.model.DefaultModelProcessor.defaultTransformerLocation"; 135 136 /** 137 * Class path location searched for transformers by default. 138 * 139 * @see #getDefaultTransformerLocation() 140 */ 141 private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc.xsl"; 142 143 /** 144 * Default transformer location. 145 */ 146 private static volatile String defaultTransformerLocation; 147 148 /** 149 * Transformer location of the instance. 150 */ 151 private String transformerLocation; 152 153 /** 154 * Creates a new {@code DefaultModelProcessor} instance. 155 */ 156 public DefaultModelProcessor() 157 { 158 super(); 159 } 160 161 /** 162 * Gets a flag indicating the processor is enabled by default. 163 * <p> 164 * The default enabled flag is controlled by system property 165 * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled} holding a value indicating the processor is 166 * enabled by default. If that property is not set, the {@code true} default is returned. 167 * </p> 168 * 169 * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by 170 * default. 171 * 172 * @see #setDefaultEnabled(java.lang.Boolean) 173 */ 174 public static boolean isDefaultEnabled() 175 { 176 if ( defaultEnabled == null ) 177 { 178 defaultEnabled = 179 Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME, 180 System.getProperty( DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME, 181 Boolean.toString( DEFAULT_ENABLED ) ) ) ); 182 183 } 184 185 return defaultEnabled; 186 } 187 188 /** 189 * Sets the flag indicating the processor is enabled by default. 190 * 191 * @param value The new value of the flag indicating the processor is enabled by default or {@code null}. 192 * 193 * @see #isDefaultEnabled() 194 */ 195 public static void setDefaultEnabled( final Boolean value ) 196 { 197 defaultEnabled = value; 198 } 199 200 /** 201 * Gets a flag indicating the processor is enabled. 202 * 203 * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled. 204 * 205 * @see #isDefaultEnabled() 206 * @see #setEnabled(java.lang.Boolean) 207 */ 208 public final boolean isEnabled() 209 { 210 if ( this.enabled == null ) 211 { 212 this.enabled = isDefaultEnabled(); 213 } 214 215 return this.enabled; 216 } 217 218 /** 219 * Sets the flag indicating the processor is enabled. 220 * 221 * @param value The new value of the flag indicating the processor is enabled or {@code null}. 222 * 223 * @see #isEnabled() 224 */ 225 public final void setEnabled( final Boolean value ) 226 { 227 this.enabled = value; 228 } 229 230 /** 231 * Gets the default location searched for transformer resources. 232 * <p> 233 * The default transformer location is controlled by system property 234 * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation} holding the location to search for 235 * transformer resources by default. If that property is not set, the {@code META-INF/jomc.xsl} default is 236 * returned. 237 * </p> 238 * 239 * @return The location searched for transformer resources by default. 240 * 241 * @see #setDefaultTransformerLocation(java.lang.String) 242 */ 243 public static String getDefaultTransformerLocation() 244 { 245 if ( defaultTransformerLocation == null ) 246 { 247 defaultTransformerLocation = 248 System.getProperty( DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME, 249 System.getProperty( DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME, 250 DEFAULT_TRANSFORMER_LOCATION ) ); 251 252 } 253 254 return defaultTransformerLocation; 255 } 256 257 /** 258 * Sets the default location searched for transformer resources. 259 * 260 * @param value The new default location to search for transformer resources or {@code null}. 261 * 262 * @see #getDefaultTransformerLocation() 263 */ 264 public static void setDefaultTransformerLocation( final String value ) 265 { 266 defaultTransformerLocation = value; 267 } 268 269 /** 270 * Gets the location searched for transformer resources. 271 * 272 * @return The location searched for transformer resources. 273 * 274 * @see #getDefaultTransformerLocation() 275 * @see #setTransformerLocation(java.lang.String) 276 */ 277 public final String getTransformerLocation() 278 { 279 if ( this.transformerLocation == null ) 280 { 281 this.transformerLocation = getDefaultTransformerLocation(); 282 } 283 284 return this.transformerLocation; 285 } 286 287 /** 288 * Sets the location searched for transformer resources. 289 * 290 * @param value The new location to search for transformer resources or {@code null}. 291 * 292 * @see #getTransformerLocation() 293 */ 294 public final void setTransformerLocation( final String value ) 295 { 296 this.transformerLocation = value; 297 } 298 299 /** 300 * Searches a given context for transformers. 301 * 302 * @param context The context to search for transformers. 303 * @param location The location to search at. 304 * 305 * @return The transformers found at {@code location} in {@code context} or {@code null}, if no transformers are 306 * found. 307 * 308 * @throws NullPointerException if {@code context} or {@code location} is {@code null}. 309 * @throws ModelException if getting the transformers fails. 310 */ 311 public List<Transformer> findTransformers( final ModelContext context, final String location ) throws ModelException 312 { 313 if ( context == null ) 314 { 315 throw new NullPointerException( "context" ); 316 } 317 if ( location == null ) 318 { 319 throw new NullPointerException( "location" ); 320 } 321 322 try 323 { 324 final long t0 = System.currentTimeMillis(); 325 final List<Transformer> transformers = new LinkedList<Transformer>(); 326 final TransformerFactory transformerFactory = TransformerFactory.newInstance(); 327 final Enumeration<URL> resources = context.findResources( location ); 328 final ErrorListener errorListener = new ErrorListener() 329 { 330 331 public void warning( final TransformerException exception ) throws TransformerException 332 { 333 if ( context.isLoggable( Level.WARNING ) ) 334 { 335 context.log( Level.WARNING, getMessage( exception ), exception ); 336 } 337 } 338 339 public void error( final TransformerException exception ) throws TransformerException 340 { 341 if ( context.isLoggable( Level.SEVERE ) ) 342 { 343 context.log( Level.SEVERE, getMessage( exception ), exception ); 344 } 345 346 throw exception; 347 } 348 349 public void fatalError( final TransformerException exception ) throws TransformerException 350 { 351 if ( context.isLoggable( Level.SEVERE ) ) 352 { 353 context.log( Level.SEVERE, getMessage( exception ), exception ); 354 } 355 356 throw exception; 357 } 358 359 }; 360 361 transformerFactory.setErrorListener( errorListener ); 362 363 int count = 0; 364 while ( resources.hasMoreElements() ) 365 { 366 count++; 367 final URL url = resources.nextElement(); 368 369 if ( context.isLoggable( Level.FINEST ) ) 370 { 371 context.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null ); 372 } 373 374 final Transformer transformer = 375 transformerFactory.newTransformer( new StreamSource( url.toURI().toASCIIString() ) ); 376 377 transformer.setErrorListener( errorListener ); 378 379 for ( final Map.Entry<Object, Object> e : System.getProperties().entrySet() ) 380 { 381 transformer.setParameter( e.getKey().toString(), e.getValue() ); 382 } 383 384 transformers.add( transformer ); 385 } 386 387 if ( context.isLoggable( Level.FINE ) ) 388 { 389 context.log( Level.FINE, getMessage( "contextReport", count, location, 390 Long.valueOf( System.currentTimeMillis() - t0 ) ), null ); 391 392 } 393 394 return transformers.isEmpty() ? null : transformers; 395 } 396 catch ( final URISyntaxException e ) 397 { 398 throw new ModelException( getMessage( e ), e ); 399 } 400 catch ( final TransformerConfigurationException e ) 401 { 402 String message = getMessage( e ); 403 if ( message == null && e.getException() != null ) 404 { 405 message = getMessage( e.getException() ); 406 } 407 408 throw new ModelException( message, e ); 409 } 410 } 411 412 /** 413 * {@inheritDoc} 414 * 415 * @see #isEnabled() 416 * @see #getTransformerLocation() 417 * @see #findTransformers(org.jomc.modlet.ModelContext, java.lang.String) 418 * @see #ENABLED_ATTRIBUTE_NAME 419 * @see #TRANSFORMER_LOCATION_ATTRIBUTE_NAME 420 */ 421 public Model processModel( final ModelContext context, final Model model ) throws ModelException 422 { 423 if ( context == null ) 424 { 425 throw new NullPointerException( "context" ); 426 } 427 if ( model == null ) 428 { 429 throw new NullPointerException( "model" ); 430 } 431 432 try 433 { 434 Model processed = model; 435 436 boolean contextEnabled = this.isEnabled(); 437 if ( DEFAULT_ENABLED == contextEnabled 438 && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean ) 439 { 440 contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME ); 441 } 442 443 String contextTransformerLocation = this.getTransformerLocation(); 444 if ( DEFAULT_TRANSFORMER_LOCATION.equals( contextTransformerLocation ) 445 && context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ) instanceof String ) 446 { 447 contextTransformerLocation = (String) context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ); 448 } 449 450 if ( contextEnabled ) 451 { 452 final org.jomc.modlet.ObjectFactory objectFactory = new org.jomc.modlet.ObjectFactory(); 453 final JAXBContext jaxbContext = context.createContext( model.getIdentifier() ); 454 final List<Transformer> transformers = this.findTransformers( context, contextTransformerLocation ); 455 processed = model.clone(); 456 457 if ( transformers != null ) 458 { 459 for ( int i = 0, s0 = transformers.size(); i < s0; i++ ) 460 { 461 final JAXBElement<Model> e = objectFactory.createModel( processed ); 462 final JAXBSource source = new JAXBSource( jaxbContext, e ); 463 final JAXBResult result = new JAXBResult( jaxbContext ); 464 transformers.get( i ).transform( source, result ); 465 466 if ( result.getResult() instanceof JAXBElement<?> 467 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Model ) 468 { 469 processed = (Model) ( (JAXBElement<?>) result.getResult() ).getValue(); 470 } 471 else 472 { 473 throw new ModelException( getMessage( 474 "illegalTransformationResult", model.getIdentifier() ) ); 475 476 } 477 } 478 } 479 } 480 else if ( context.isLoggable( Level.FINER ) ) 481 { 482 context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(), 483 model.getIdentifier() ), null ); 484 485 } 486 487 return processed; 488 } 489 catch ( final TransformerException e ) 490 { 491 String message = getMessage( e ); 492 if ( message == null && e.getException() != null ) 493 { 494 message = getMessage( e.getException() ); 495 } 496 497 throw new ModelException( message, e ); 498 } 499 catch ( final JAXBException e ) 500 { 501 String message = getMessage( e ); 502 if ( message == null && e.getLinkedException() != null ) 503 { 504 message = getMessage( e.getLinkedException() ); 505 } 506 507 throw new ModelException( message, e ); 508 } 509 } 510 511 private static String getMessage( final String key, final Object... args ) 512 { 513 return MessageFormat.format( ResourceBundle.getBundle( 514 DefaultModelProcessor.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args ); 515 516 } 517 518 private static String getMessage( final Throwable t ) 519 { 520 return t != null 521 ? t.getMessage() != null && t.getMessage().trim().length() > 0 522 ? t.getMessage() 523 : getMessage( t.getCause() ) 524 : null; 525 526 } 527 528}