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