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: DefaultModelProvider.java 5374 2016-09-05 12:24:12Z schulte $ 029 * 030 */ 031package org.jomc.model.modlet; 032 033import java.lang.reflect.UndeclaredThrowableException; 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.ResourceBundle; 041import java.util.concurrent.Callable; 042import java.util.concurrent.CancellationException; 043import java.util.concurrent.ExecutionException; 044import java.util.concurrent.Future; 045import java.util.logging.Level; 046import javax.xml.bind.JAXBElement; 047import javax.xml.bind.JAXBException; 048import javax.xml.bind.UnmarshalException; 049import javax.xml.bind.Unmarshaller; 050import javax.xml.validation.Schema; 051import org.jomc.model.Module; 052import org.jomc.model.Modules; 053import org.jomc.model.Text; 054import org.jomc.model.Texts; 055import org.jomc.modlet.Model; 056import org.jomc.modlet.ModelContext; 057import org.jomc.modlet.ModelException; 058import org.jomc.modlet.ModelProvider; 059 060/** 061 * Default object management and configuration {@code ModelProvider} implementation. 062 * 063 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 064 * @version $JOMC: DefaultModelProvider.java 5374 2016-09-05 12:24:12Z schulte $ 065 * @see ModelContext#findModel(java.lang.String) 066 */ 067public class DefaultModelProvider implements ModelProvider 068{ 069 070 /** 071 * Constant for the name of the model context attribute backing property {@code enabled}. 072 * 073 * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model) 074 * @see ModelContext#getAttribute(java.lang.String) 075 * @since 1.2 076 */ 077 public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProvider.enabledAttribute"; 078 079 /** 080 * Constant for the name of the system property controlling property {@code defaultEnabled}. 081 * 082 * @see #isDefaultEnabled() 083 */ 084 private static final String DEFAULT_ENABLED_PROPERTY_NAME = 085 "org.jomc.model.modlet.DefaultModelProvider.defaultEnabled"; 086 087 /** 088 * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}. 089 * @see #isDefaultEnabled() 090 */ 091 private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME = 092 "org.jomc.model.DefaultModelProvider.defaultEnabled"; 093 094 /** 095 * Default value of the flag indicating the provider is enabled by default. 096 * 097 * @see #isDefaultEnabled() 098 * @since 1.2 099 */ 100 private static final Boolean DEFAULT_ENABLED = Boolean.TRUE; 101 102 /** 103 * Flag indicating the provider is enabled by default. 104 */ 105 private static volatile Boolean defaultEnabled; 106 107 /** 108 * Flag indicating the provider is enabled. 109 */ 110 private Boolean enabled; 111 112 /** 113 * Constant for the name of the model context attribute backing property {@code moduleLocation}. 114 * 115 * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model) 116 * @see ModelContext#getAttribute(java.lang.String) 117 * @since 1.2 118 */ 119 public static final String MODULE_LOCATION_ATTRIBUTE_NAME = 120 "org.jomc.model.modlet.DefaultModelProvider.moduleLocationAttribute"; 121 122 /** 123 * Constant for the name of the system property controlling property {@code defaultModuleLocation}. 124 * 125 * @see #getDefaultModuleLocation() 126 */ 127 private static final String DEFAULT_MODULE_LOCATION_PROPERTY_NAME = 128 "org.jomc.model.modlet.DefaultModelProvider.defaultModuleLocation"; 129 130 /** 131 * Constant for the name of the deprecated system property controlling property {@code defaultModuleLocation}. 132 * @see #getDefaultModuleLocation() 133 */ 134 private static final String DEPRECATED_DEFAULT_MODULE_LOCATION_PROPERTY_NAME = 135 "org.jomc.model.DefaultModelProvider.defaultModuleLocation"; 136 137 /** 138 * Class path location searched for modules by default. 139 * 140 * @see #getDefaultModuleLocation() 141 */ 142 private static final String DEFAULT_MODULE_LOCATION = "META-INF/jomc.xml"; 143 144 /** 145 * Default module location. 146 */ 147 private static volatile String defaultModuleLocation; 148 149 /** 150 * Module location of the instance. 151 */ 152 private String moduleLocation; 153 154 /** 155 * Constant for the name of the model context attribute backing property {@code validating}. 156 * 157 * @see #findModules(org.jomc.modlet.ModelContext, java.lang.String, java.lang.String) 158 * @see ModelContext#getAttribute(java.lang.String) 159 * @since 1.2 160 */ 161 public static final String VALIDATING_ATTRIBUTE_NAME = 162 "org.jomc.model.modlet.DefaultModelProvider.validatingAttribute"; 163 164 /** 165 * Constant for the name of the system property controlling property {@code defaultValidating}. 166 * 167 * @see #isDefaultValidating() 168 * @since 1.2 169 */ 170 private static final String DEFAULT_VALIDATING_PROPERTY_NAME = 171 "org.jomc.model.modlet.DefaultModelProvider.defaultValidating"; 172 173 /** 174 * Default value of the flag indicating the provider is validating resources by default. 175 * 176 * @see #isDefaultValidating() 177 * @since 1.2 178 */ 179 private static final Boolean DEFAULT_VALIDATING = Boolean.TRUE; 180 181 /** 182 * Flag indicating the provider is validating resources by default. 183 * 184 * @since 1.2 185 */ 186 private static volatile Boolean defaultValidating; 187 188 /** 189 * Flag indicating the provider is validating resources. 190 * 191 * @since 1.2 192 */ 193 private Boolean validating; 194 195 /** 196 * Creates a new {@code DefaultModelProvider} instance. 197 */ 198 public DefaultModelProvider() 199 { 200 super(); 201 } 202 203 /** 204 * Gets a flag indicating the provider is enabled by default. 205 * <p> 206 * The default enabled flag is controlled by system property 207 * {@code org.jomc.model.modlet.DefaultModelProvider.defaultEnabled} holding a value indicating the provider is 208 * enabled by default. If that property is not set, the {@code true} default is returned. 209 * </p> 210 * 211 * @return {@code true}, if the provider is enabled by default; {@code false}, if the provider is disabled by 212 * default. 213 * 214 * @see #setDefaultEnabled(java.lang.Boolean) 215 */ 216 public static boolean isDefaultEnabled() 217 { 218 if ( defaultEnabled == null ) 219 { 220 defaultEnabled = 221 Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME, 222 System.getProperty( DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME, 223 Boolean.toString( DEFAULT_ENABLED ) ) ) ); 224 225 } 226 227 return defaultEnabled; 228 } 229 230 /** 231 * Sets the flag indicating the provider is enabled by default. 232 * 233 * @param value The new value of the flag indicating the provider is enabled by default or {@code null}. 234 * 235 * @see #isDefaultEnabled() 236 */ 237 public static void setDefaultEnabled( final Boolean value ) 238 { 239 defaultEnabled = value; 240 } 241 242 /** 243 * Gets a flag indicating the provider is enabled. 244 * 245 * @return {@code true}, if the provider is enabled; {@code false}, if the provider is disabled. 246 * 247 * @see #isDefaultEnabled() 248 * @see #setEnabled(java.lang.Boolean) 249 */ 250 public final boolean isEnabled() 251 { 252 if ( this.enabled == null ) 253 { 254 this.enabled = isDefaultEnabled(); 255 } 256 257 return this.enabled; 258 } 259 260 /** 261 * Sets the flag indicating the provider is enabled. 262 * 263 * @param value The new value of the flag indicating the provider is enabled or {@code null}. 264 * 265 * @see #isEnabled() 266 */ 267 public final void setEnabled( final Boolean value ) 268 { 269 this.enabled = value; 270 } 271 272 /** 273 * Gets the default location searched for module resources. 274 * <p> 275 * The default module location is controlled by system property 276 * {@code org.jomc.model.modlet.DefaultModelProvider.defaultModuleLocation} holding the location to search for 277 * module resources by default. If that property is not set, the {@code META-INF/jomc.xml} default is returned. 278 * </p> 279 * 280 * @return The location searched for module resources by default. 281 * 282 * @see #setDefaultModuleLocation(java.lang.String) 283 */ 284 public static String getDefaultModuleLocation() 285 { 286 if ( defaultModuleLocation == null ) 287 { 288 defaultModuleLocation = 289 System.getProperty( DEFAULT_MODULE_LOCATION_PROPERTY_NAME, 290 System.getProperty( DEPRECATED_DEFAULT_MODULE_LOCATION_PROPERTY_NAME, 291 DEFAULT_MODULE_LOCATION ) ); 292 293 } 294 295 return defaultModuleLocation; 296 } 297 298 /** 299 * Sets the default location searched for module resources. 300 * 301 * @param value The new default location to search for module resources or {@code null}. 302 * 303 * @see #getDefaultModuleLocation() 304 */ 305 public static void setDefaultModuleLocation( final String value ) 306 { 307 defaultModuleLocation = value; 308 } 309 310 /** 311 * Gets the location searched for module resources. 312 * 313 * @return The location searched for module resources. 314 * 315 * @see #getDefaultModuleLocation() 316 * @see #setModuleLocation(java.lang.String) 317 */ 318 public final String getModuleLocation() 319 { 320 if ( this.moduleLocation == null ) 321 { 322 this.moduleLocation = getDefaultModuleLocation(); 323 } 324 325 return this.moduleLocation; 326 } 327 328 /** 329 * Sets the location searched for module resources. 330 * 331 * @param value The new location to search for module resources or {@code null}. 332 * 333 * @see #getModuleLocation() 334 */ 335 public final void setModuleLocation( final String value ) 336 { 337 this.moduleLocation = value; 338 } 339 340 /** 341 * Gets a flag indicating the provider is validating resources by default. 342 * <p> 343 * The default validating flag is controlled by system property 344 * {@code org.jomc.model.modlet.DefaultModelProvider.defaultValidating} holding a value indicating the provider is 345 * validating resources by default. If that property is not set, the {@code true} default is returned. 346 * </p> 347 * 348 * @return {@code true}, if the provider is validating resources by default; {@code false}, if the provider is not 349 * validating resources by default. 350 * 351 * @see #isValidating() 352 * @see #setDefaultValidating(java.lang.Boolean) 353 * 354 * @since 1.2 355 */ 356 public static boolean isDefaultValidating() 357 { 358 if ( defaultValidating == null ) 359 { 360 defaultValidating = Boolean.valueOf( System.getProperty( 361 DEFAULT_VALIDATING_PROPERTY_NAME, Boolean.toString( DEFAULT_VALIDATING ) ) ); 362 363 } 364 365 return defaultValidating; 366 } 367 368 /** 369 * Sets the flag indicating the provider is validating resources by default. 370 * 371 * @param value The new value of the flag indicating the provider is validating resources by default or 372 * {@code null}. 373 * 374 * @see #isDefaultValidating() 375 * 376 * @since 1.2 377 */ 378 public static void setDefaultValidating( final Boolean value ) 379 { 380 defaultValidating = value; 381 } 382 383 /** 384 * Gets a flag indicating the provider is validating resources. 385 * 386 * @return {@code true}, if the provider is validating resources; {@code false}, if the provider is not validating 387 * resources. 388 * 389 * @see #isDefaultValidating() 390 * @see #setValidating(java.lang.Boolean) 391 * 392 * @since 1.2 393 */ 394 public final boolean isValidating() 395 { 396 if ( this.validating == null ) 397 { 398 this.validating = isDefaultValidating(); 399 } 400 401 return this.validating; 402 } 403 404 /** 405 * Sets the flag indicating the provider is validating resources. 406 * 407 * @param value The new value of the flag indicating the provider is validating resources or {@code null}. 408 * 409 * @see #isValidating() 410 * 411 * @since 1.2 412 */ 413 public final void setValidating( final Boolean value ) 414 { 415 this.validating = value; 416 } 417 418 /** 419 * Searches a given context for modules. 420 * 421 * @param context The context to search for modules. 422 * @param model The identifier of the model to search for modules. 423 * @param location The location to search at. 424 * 425 * @return The modules found at {@code location} in {@code context} or {@code null}, if no modules are found. 426 * 427 * @throws NullPointerException if {@code context}, {@code model} or {@code location} is {@code null}. 428 * @throws ModelException if searching the context fails. 429 * 430 * @see #isValidating() 431 * @see #VALIDATING_ATTRIBUTE_NAME 432 */ 433 public Modules findModules( final ModelContext context, final String model, final String location ) 434 throws ModelException 435 { 436 if ( context == null ) 437 { 438 throw new NullPointerException( "context" ); 439 } 440 if ( model == null ) 441 { 442 throw new NullPointerException( "model" ); 443 } 444 if ( location == null ) 445 { 446 throw new NullPointerException( "location" ); 447 } 448 449 try 450 { 451 boolean contextValidating = this.isValidating(); 452 if ( DEFAULT_VALIDATING == contextValidating 453 && context.getAttribute( VALIDATING_ATTRIBUTE_NAME ) instanceof Boolean ) 454 { 455 contextValidating = (Boolean) context.getAttribute( VALIDATING_ATTRIBUTE_NAME ); 456 } 457 458 final long t0 = System.nanoTime(); 459 final Text text = new Text(); 460 text.setLanguage( "en" ); 461 text.setValue( getMessage( "contextModulesInfo", location ) ); 462 463 final Modules modules = new Modules(); 464 modules.setDocumentation( new Texts() ); 465 modules.getDocumentation().setDefaultLanguage( "en" ); 466 modules.getDocumentation().getText().add( text ); 467 468 final ThreadLocal<Unmarshaller> threadLocalUnmarshaller = new ThreadLocal<Unmarshaller>(); 469 final Schema schema = contextValidating ? context.createSchema( model ) : null; 470 471 class UnmarshallTask implements Callable<Module> 472 { 473 474 private final URL resource; 475 476 UnmarshallTask( final URL resource ) 477 { 478 super(); 479 this.resource = resource; 480 } 481 482 public Module call() throws ModelException 483 { 484 try 485 { 486 Module module = null; 487 488 if ( context.isLoggable( Level.FINEST ) ) 489 { 490 context.log( Level.FINEST, getMessage( "processing", this.resource.toExternalForm() ), 491 null ); 492 493 } 494 495 Unmarshaller u = threadLocalUnmarshaller.get(); 496 if ( u == null ) 497 { 498 u = context.createUnmarshaller( model ); 499 u.setSchema( schema ); 500 threadLocalUnmarshaller.set( u ); 501 } 502 503 Object content = u.unmarshal( this.resource ); 504 if ( content instanceof JAXBElement<?> ) 505 { 506 content = ( (JAXBElement<?>) content ).getValue(); 507 } 508 509 if ( content instanceof Module ) 510 { 511 final Module m = (Module) content; 512 513 if ( context.isLoggable( Level.FINEST ) ) 514 { 515 context.log( Level.FINEST, getMessage( 516 "foundModule", m.getName(), m.getVersion() == null ? "" : m.getVersion() ), 517 null ); 518 519 } 520 521 module = m; 522 } 523 else if ( context.isLoggable( Level.WARNING ) ) 524 { 525 context.log( Level.WARNING, getMessage( "ignoringDocument", 526 content == null ? "<>" : content.toString(), 527 this.resource.toExternalForm() ), null ); 528 529 } 530 531 return module; 532 } 533 catch ( final UnmarshalException e ) 534 { 535 String message = getMessage( e ); 536 if ( message == null && e.getLinkedException() != null ) 537 { 538 message = getMessage( e.getLinkedException() ); 539 } 540 541 message = getMessage( "unmarshalException", this.resource.toExternalForm(), 542 message != null ? " " + message : "" ); 543 544 throw new ModelException( message, e ); 545 } 546 catch ( final JAXBException e ) 547 { 548 String message = getMessage( e ); 549 if ( message == null && e.getLinkedException() != null ) 550 { 551 message = getMessage( e.getLinkedException() ); 552 } 553 554 throw new ModelException( message, e ); 555 } 556 } 557 558 } 559 560 final List<UnmarshallTask> tasks = new LinkedList<UnmarshallTask>(); 561 final Enumeration<URL> resources = context.findResources( location ); 562 563 while ( resources.hasMoreElements() ) 564 { 565 tasks.add( new UnmarshallTask( resources.nextElement() ) ); 566 } 567 568 int count = 0; 569 if ( context.getExecutorService() != null && tasks.size() > 1 ) 570 { 571 for ( final Future<Module> task : context.getExecutorService().invokeAll( tasks ) ) 572 { 573 final Module m = task.get(); 574 575 if ( m != null ) 576 { 577 modules.getModule().add( m ); 578 count++; 579 } 580 } 581 } 582 else 583 { 584 for ( final UnmarshallTask task : tasks ) 585 { 586 final Module m = task.call(); 587 if ( m != null ) 588 { 589 modules.getModule().add( m ); 590 count++; 591 } 592 } 593 } 594 595 if ( context.isLoggable( Level.FINE ) ) 596 { 597 context.log( Level.FINE, getMessage( "contextReport", count, location, System.nanoTime() - t0 ), null ); 598 } 599 600 return modules.getModule().isEmpty() ? null : modules; 601 } 602 catch ( final CancellationException e ) 603 { 604 throw new ModelException( getMessage( e ), e ); 605 } 606 catch ( final InterruptedException e ) 607 { 608 throw new ModelException( getMessage( e ), e ); 609 } 610 catch ( final ExecutionException e ) 611 { 612 if ( e.getCause() instanceof ModelException ) 613 { 614 throw (ModelException) e.getCause(); 615 } 616 else if ( e.getCause() instanceof RuntimeException ) 617 { 618 // The fork-join framework breaks the exception handling contract of Callable by re-throwing any 619 // exception caught using a runtime exception. 620 if ( e.getCause().getCause() instanceof ModelException ) 621 { 622 throw (ModelException) e.getCause().getCause(); 623 } 624 else if ( e.getCause().getCause() instanceof RuntimeException ) 625 { 626 throw (RuntimeException) e.getCause().getCause(); 627 } 628 else if ( e.getCause().getCause() instanceof Error ) 629 { 630 throw (Error) e.getCause().getCause(); 631 } 632 else if ( e.getCause().getCause() instanceof Exception ) 633 { 634 // Checked exception not declared to be thrown by the Callable's 'call' method. 635 throw new UndeclaredThrowableException( e.getCause().getCause() ); 636 } 637 else 638 { 639 throw (RuntimeException) e.getCause(); 640 } 641 } 642 else if ( e.getCause() instanceof Error ) 643 { 644 throw (Error) e.getCause(); 645 } 646 else 647 { 648 // Checked exception not declared to be thrown by the Callable's 'call' method. 649 throw new UndeclaredThrowableException( e.getCause() ); 650 } 651 } 652 } 653 654 /** 655 * {@inheritDoc} 656 * 657 * @return The {@code Model} found in the context or {@code null}, if no {@code Model} is found or the provider is 658 * disabled. 659 * 660 * @see #isEnabled() 661 * @see #getModuleLocation() 662 * @see #findModules(org.jomc.modlet.ModelContext, java.lang.String, java.lang.String) 663 * @see #ENABLED_ATTRIBUTE_NAME 664 * @see #MODULE_LOCATION_ATTRIBUTE_NAME 665 */ 666 public Model findModel( final ModelContext context, final Model model ) throws ModelException 667 { 668 if ( context == null ) 669 { 670 throw new NullPointerException( "context" ); 671 } 672 if ( model == null ) 673 { 674 throw new NullPointerException( "model" ); 675 } 676 677 Model found = null; 678 679 boolean contextEnabled = this.isEnabled(); 680 if ( DEFAULT_ENABLED == contextEnabled && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean ) 681 { 682 contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME ); 683 } 684 685 String contextModuleLocation = this.getModuleLocation(); 686 if ( DEFAULT_MODULE_LOCATION.equals( contextModuleLocation ) 687 && context.getAttribute( MODULE_LOCATION_ATTRIBUTE_NAME ) instanceof String ) 688 { 689 contextModuleLocation = (String) context.getAttribute( MODULE_LOCATION_ATTRIBUTE_NAME ); 690 } 691 692 if ( contextEnabled ) 693 { 694 final Modules modules = this.findModules( context, model.getIdentifier(), contextModuleLocation ); 695 696 if ( modules != null ) 697 { 698 found = model.clone(); 699 ModelHelper.addModules( found, modules ); 700 } 701 } 702 else if ( context.isLoggable( Level.FINER ) ) 703 { 704 context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(), 705 model.getIdentifier() ), null ); 706 707 } 708 709 return found; 710 } 711 712 private static String getMessage( final String key, final Object... args ) 713 { 714 return MessageFormat.format( ResourceBundle.getBundle( 715 DefaultModelProvider.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args ); 716 717 } 718 719 private static String getMessage( final Throwable t ) 720 { 721 return t != null 722 ? t.getMessage() != null && t.getMessage().trim().length() > 0 723 ? t.getMessage() 724 : getMessage( t.getCause() ) 725 : null; 726 727 } 728 729}