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 5355 2016-09-05 05:21:13Z schulte $ 029 * 030 */ 031package org.jomc.model.modlet; 032 033import java.io.ByteArrayInputStream; 034import java.io.ByteArrayOutputStream; 035import java.io.IOException; 036import java.lang.reflect.UndeclaredThrowableException; 037import java.net.URISyntaxException; 038import java.net.URL; 039import java.text.MessageFormat; 040import java.util.Enumeration; 041import java.util.LinkedList; 042import java.util.List; 043import java.util.Locale; 044import java.util.Map; 045import java.util.Properties; 046import java.util.ResourceBundle; 047import java.util.concurrent.Callable; 048import java.util.concurrent.CancellationException; 049import java.util.concurrent.ExecutionException; 050import java.util.concurrent.Future; 051import java.util.logging.Level; 052import javax.xml.bind.JAXBContext; 053import javax.xml.bind.JAXBElement; 054import javax.xml.bind.JAXBException; 055import javax.xml.bind.util.JAXBResult; 056import javax.xml.bind.util.JAXBSource; 057import javax.xml.transform.ErrorListener; 058import javax.xml.transform.Transformer; 059import javax.xml.transform.TransformerConfigurationException; 060import javax.xml.transform.TransformerException; 061import javax.xml.transform.TransformerFactory; 062import javax.xml.transform.stream.StreamSource; 063import org.jomc.modlet.Model; 064import org.jomc.modlet.ModelContext; 065import org.jomc.modlet.ModelException; 066import org.jomc.modlet.ModelProcessor; 067 068/** 069 * Default object management and configuration {@code ModelProcessor} implementation. 070 * 071 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 072 * @version $JOMC: DefaultModelProcessor.java 5355 2016-09-05 05:21:13Z schulte $ 073 * @see ModelContext#processModel(org.jomc.modlet.Model) 074 */ 075public class DefaultModelProcessor implements ModelProcessor 076{ 077 078 /** 079 * Constant for the name of the model context attribute backing property {@code enabled}. 080 * 081 * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model) 082 * @see ModelContext#getAttribute(java.lang.String) 083 * @since 1.2 084 */ 085 public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProcessor.enabledAttribute"; 086 087 /** 088 * Constant for the name of the system property controlling property {@code defaultEnabled}. 089 * 090 * @see #isDefaultEnabled() 091 */ 092 private static final String DEFAULT_ENABLED_PROPERTY_NAME = 093 "org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled"; 094 095 /** 096 * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}. 097 * @see #isDefaultEnabled() 098 */ 099 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}