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: MergeModulesTask.java 4200 2012-01-25 09:46:13Z schulte2005 $ 029 * 030 */ 031 package org.jomc.ant; 032 033 import java.io.ByteArrayOutputStream; 034 import java.io.File; 035 import java.io.IOException; 036 import java.io.InputStream; 037 import java.io.OutputStreamWriter; 038 import java.net.SocketTimeoutException; 039 import java.net.URISyntaxException; 040 import java.net.URL; 041 import java.net.URLConnection; 042 import java.util.ArrayList; 043 import java.util.HashSet; 044 import java.util.Iterator; 045 import java.util.LinkedList; 046 import java.util.List; 047 import java.util.Set; 048 import java.util.logging.Level; 049 import javax.xml.bind.JAXBElement; 050 import javax.xml.bind.JAXBException; 051 import javax.xml.bind.Marshaller; 052 import javax.xml.bind.Unmarshaller; 053 import javax.xml.bind.util.JAXBResult; 054 import javax.xml.bind.util.JAXBSource; 055 import javax.xml.transform.Source; 056 import javax.xml.transform.Transformer; 057 import javax.xml.transform.TransformerConfigurationException; 058 import javax.xml.transform.TransformerException; 059 import javax.xml.transform.stream.StreamSource; 060 import org.apache.tools.ant.BuildException; 061 import org.apache.tools.ant.Project; 062 import org.jomc.ant.types.NameType; 063 import org.jomc.ant.types.ResourceType; 064 import org.jomc.ant.types.TransformerResourceType; 065 import org.jomc.model.Module; 066 import org.jomc.model.Modules; 067 import org.jomc.model.ObjectFactory; 068 import org.jomc.model.modlet.DefaultModelProvider; 069 import org.jomc.modlet.ModelContext; 070 import org.jomc.modlet.ModelException; 071 import org.jomc.modlet.ModelValidationReport; 072 073 /** 074 * Task for merging module resources. 075 * 076 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a> 077 * @version $JOMC: MergeModulesTask.java 4200 2012-01-25 09:46:13Z schulte2005 $ 078 */ 079 public final class MergeModulesTask extends JomcModelTask 080 { 081 082 /** The encoding of the module resource. */ 083 private String moduleEncoding; 084 085 /** File to write the merged module to. */ 086 private File moduleFile; 087 088 /** The name of the merged module. */ 089 private String moduleName; 090 091 /** The version of the merged module. */ 092 private String moduleVersion; 093 094 /** The vendor of the merged module. */ 095 private String moduleVendor; 096 097 /** Included modules. */ 098 private Set<NameType> moduleIncludes; 099 100 /** Excluded modules. */ 101 private Set<NameType> moduleExcludes; 102 103 /** XSLT documents to use for transforming model objects. */ 104 private List<TransformerResourceType> modelObjectStylesheetResources; 105 106 /** Creates a new {@code MergeModulesTask} instance. */ 107 public MergeModulesTask() 108 { 109 super(); 110 } 111 112 /** 113 * Gets the file to write the merged module to. 114 * 115 * @return The file to write the merged module to or {@code null}. 116 * 117 * @see #setModuleFile(java.io.File) 118 */ 119 public File getModuleFile() 120 { 121 return this.moduleFile; 122 } 123 124 /** 125 * Sets the file to write the merged module to. 126 * 127 * @param value The new file to write the merged module to or {@code null}. 128 * 129 * @see #getModuleFile() 130 */ 131 public void setModuleFile( final File value ) 132 { 133 this.moduleFile = value; 134 } 135 136 /** 137 * Gets the encoding of the module resource. 138 * 139 * @return The encoding of the module resource. 140 * 141 * @see #setModuleEncoding(java.lang.String) 142 */ 143 public String getModuleEncoding() 144 { 145 if ( this.moduleEncoding == null ) 146 { 147 this.moduleEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding(); 148 } 149 150 return this.moduleEncoding; 151 } 152 153 /** 154 * Sets the encoding of the module resource. 155 * 156 * @param value The new encoding of the module resource or {@code null}. 157 * 158 * @see #getModuleEncoding() 159 */ 160 public void setModuleEncoding( final String value ) 161 { 162 this.moduleEncoding = value; 163 } 164 165 /** 166 * Gets the name of the merged module. 167 * 168 * @return The name of the merged module or {@code null}. 169 * 170 * @see #setModuleName(java.lang.String) 171 */ 172 public String getModuleName() 173 { 174 return this.moduleName; 175 } 176 177 /** 178 * Sets the name of the merged module. 179 * 180 * @param value The new name of the merged module or {@code null}. 181 * 182 * @see #getModuleName() 183 */ 184 public void setModuleName( final String value ) 185 { 186 this.moduleName = value; 187 } 188 189 /** 190 * Gets the version of the merged module. 191 * 192 * @return The version of the merged module or {@code null}. 193 * 194 * @see #setModuleVersion(java.lang.String) 195 */ 196 public String getModuleVersion() 197 { 198 return this.moduleVersion; 199 } 200 201 /** 202 * Sets the version of the merged module. 203 * 204 * @param value The new version of the merged module or {@code null}. 205 * 206 * @see #getModuleVersion() 207 */ 208 public void setModuleVersion( final String value ) 209 { 210 this.moduleVersion = value; 211 } 212 213 /** 214 * Gets the vendor of the merged module. 215 * 216 * @return The vendor of the merge module or {@code null}. 217 * 218 * @see #setModuleVendor(java.lang.String) 219 */ 220 public String getModuleVendor() 221 { 222 return this.moduleVendor; 223 } 224 225 /** 226 * Sets the vendor of the merged module. 227 * 228 * @param value The new vendor of the merged module or {@code null}. 229 * 230 * @see #getModuleVendor() 231 */ 232 public void setModuleVendor( final String value ) 233 { 234 this.moduleVendor = value; 235 } 236 237 /** 238 * Gets a set of module names to include. 239 * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make 240 * to the returned set will be present inside the object. This is why there is no {@code set} method for the 241 * module includes property.</p> 242 * 243 * @return A set of module names to include. 244 * 245 * @see #createModuleInclude() 246 */ 247 public Set<NameType> getModuleIncludes() 248 { 249 if ( this.moduleIncludes == null ) 250 { 251 this.moduleIncludes = new HashSet<NameType>(); 252 } 253 254 return this.moduleIncludes; 255 } 256 257 /** 258 * Creates a new {@code moduleInclude} element instance. 259 * 260 * @return A new {@code moduleInclude} element instance. 261 * 262 * @see #getModuleIncludes() 263 */ 264 public NameType createModuleInclude() 265 { 266 final NameType moduleInclude = new NameType(); 267 this.getModuleIncludes().add( moduleInclude ); 268 return moduleInclude; 269 } 270 271 /** 272 * Gets a set of module names to exclude. 273 * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make 274 * to the returned set will be present inside the object. This is why there is no {@code set} method for the 275 * module excludes property.</p> 276 * 277 * @return A set of module names to exclude. 278 * 279 * @see #createModuleExclude() 280 */ 281 public Set<NameType> getModuleExcludes() 282 { 283 if ( this.moduleExcludes == null ) 284 { 285 this.moduleExcludes = new HashSet<NameType>(); 286 } 287 288 return this.moduleExcludes; 289 } 290 291 /** 292 * Creates a new {@code moduleExclude} element instance. 293 * 294 * @return A new {@code moduleExclude} element instance. 295 * 296 * @see #getModuleExcludes() 297 */ 298 public NameType createModuleExclude() 299 { 300 final NameType moduleExclude = new NameType(); 301 this.getModuleExcludes().add( moduleExclude ); 302 return moduleExclude; 303 } 304 305 /** 306 * Gets the XSLT documents to use for transforming model objects. 307 * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make 308 * to the returned list will be present inside the object. This is why there is no {@code set} method for the 309 * model object stylesheet resources property.</p> 310 * 311 * @return The XSLT documents to use for transforming model objects. 312 * 313 * @see #createModelObjectStylesheetResource() 314 */ 315 public List<TransformerResourceType> getModelObjectStylesheetResources() 316 { 317 if ( this.modelObjectStylesheetResources == null ) 318 { 319 this.modelObjectStylesheetResources = new LinkedList<TransformerResourceType>(); 320 } 321 322 return this.modelObjectStylesheetResources; 323 } 324 325 /** 326 * Creates a new {@code modelObjectStylesheetResource} element instance. 327 * 328 * @return A new {@code modelObjectStylesheetResource} element instance. 329 * 330 * @see #getModelObjectStylesheetResources() 331 */ 332 public TransformerResourceType createModelObjectStylesheetResource() 333 { 334 final TransformerResourceType modelObjectStylesheetResource = new TransformerResourceType(); 335 this.getModelObjectStylesheetResources().add( modelObjectStylesheetResource ); 336 return modelObjectStylesheetResource; 337 } 338 339 /** {@inheritDoc} */ 340 @Override 341 public void preExecuteTask() throws BuildException 342 { 343 super.preExecuteTask(); 344 345 this.assertNotNull( "moduleFile", this.getModuleFile() ); 346 this.assertNotNull( "moduleName", this.getModuleName() ); 347 this.assertNamesNotNull( this.getModuleExcludes() ); 348 this.assertNamesNotNull( this.getModuleIncludes() ); 349 this.assertLocationsNotNull( this.getModelObjectStylesheetResources() ); 350 } 351 352 /** 353 * Merges module resources. 354 * 355 * @throws BuildException if merging module resources fails. 356 */ 357 @Override 358 public void executeTask() throws BuildException 359 { 360 ProjectClassLoader classLoader = null; 361 boolean suppressExceptionOnClose = true; 362 363 try 364 { 365 this.log( Messages.getMessage( "mergingModules", this.getModel() ) ); 366 367 classLoader = this.newProjectClassLoader(); 368 final Modules modules = new Modules(); 369 final Set<ResourceType> resources = new HashSet<ResourceType>( this.getModuleResources() ); 370 final ModelContext context = this.newModelContext( classLoader ); 371 final Marshaller marshaller = context.createMarshaller( this.getModel() ); 372 final Unmarshaller unmarshaller = context.createUnmarshaller( this.getModel() ); 373 374 if ( this.isModelResourceValidationEnabled() ) 375 { 376 unmarshaller.setSchema( context.createSchema( this.getModel() ) ); 377 } 378 379 if ( resources.isEmpty() ) 380 { 381 final ResourceType defaultResource = new ResourceType(); 382 defaultResource.setLocation( DefaultModelProvider.getDefaultModuleLocation() ); 383 defaultResource.setOptional( true ); 384 resources.add( defaultResource ); 385 } 386 387 for ( ResourceType resource : resources ) 388 { 389 final URL[] urls = this.getResources( context, resource.getLocation() ); 390 391 if ( urls.length == 0 ) 392 { 393 if ( resource.isOptional() ) 394 { 395 this.logMessage( Level.WARNING, Messages.getMessage( "moduleResourceNotFound", 396 resource.getLocation() ) ); 397 398 } 399 else 400 { 401 throw new BuildException( 402 Messages.getMessage( "moduleResourceNotFound", resource.getLocation() ), 403 this.getLocation() ); 404 405 } 406 } 407 408 for ( int i = urls.length - 1; i >= 0; i-- ) 409 { 410 InputStream in = null; 411 suppressExceptionOnClose = true; 412 413 try 414 { 415 this.logMessage( Level.FINEST, Messages.getMessage( "reading", urls[i].toExternalForm() ) ); 416 417 final URLConnection con = urls[i].openConnection(); 418 con.setConnectTimeout( resource.getConnectTimeout() ); 419 con.setReadTimeout( resource.getReadTimeout() ); 420 con.connect(); 421 in = con.getInputStream(); 422 423 final Source source = new StreamSource( in, urls[i].toURI().toASCIIString() ); 424 425 Object o = unmarshaller.unmarshal( source ); 426 if ( o instanceof JAXBElement<?> ) 427 { 428 o = ( (JAXBElement<?>) o ).getValue(); 429 } 430 431 if ( o instanceof Module ) 432 { 433 modules.getModule().add( (Module) o ); 434 } 435 else if ( o instanceof Modules ) 436 { 437 modules.getModule().addAll( ( (Modules) o ).getModule() ); 438 } 439 else 440 { 441 this.log( Messages.getMessage( "unsupportedModuleResource", urls[i].toExternalForm() ), 442 Project.MSG_WARN ); 443 444 } 445 446 suppressExceptionOnClose = false; 447 } 448 catch ( final SocketTimeoutException e ) 449 { 450 String message = Messages.getMessage( e ); 451 message = Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ); 452 453 if ( resource.isOptional() ) 454 { 455 this.getProject().log( message, e, Project.MSG_WARN ); 456 } 457 else 458 { 459 throw new BuildException( message, e, this.getLocation() ); 460 } 461 } 462 catch ( final IOException e ) 463 { 464 String message = Messages.getMessage( e ); 465 message = Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ); 466 467 if ( resource.isOptional() ) 468 { 469 this.getProject().log( message, e, Project.MSG_WARN ); 470 } 471 else 472 { 473 throw new BuildException( message, e, this.getLocation() ); 474 } 475 } 476 finally 477 { 478 try 479 { 480 if ( in != null ) 481 { 482 in.close(); 483 } 484 } 485 catch ( final IOException e ) 486 { 487 488 if ( suppressExceptionOnClose ) 489 { 490 this.logMessage( Level.SEVERE, Messages.getMessage( e ), e ); 491 } 492 else 493 { 494 throw new BuildException( Messages.getMessage( e ), e, this.getLocation() ); 495 } 496 } 497 } 498 } 499 500 suppressExceptionOnClose = true; 501 } 502 503 for ( final Iterator<Module> it = modules.getModule().iterator(); it.hasNext(); ) 504 { 505 final Module module = it.next(); 506 507 if ( !this.isModuleIncluded( module ) || this.isModuleExcluded( module ) ) 508 { 509 it.remove(); 510 this.log( Messages.getMessage( "excludingModule", module.getName() ) ); 511 } 512 else 513 { 514 this.log( Messages.getMessage( "includingModule", module.getName() ) ); 515 } 516 } 517 518 Module classpathModule = null; 519 if ( this.isModelObjectClasspathResolutionEnabled() ) 520 { 521 classpathModule = modules.getClasspathModule( Modules.getDefaultClasspathModuleName(), classLoader ); 522 523 if ( classpathModule != null && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null ) 524 { 525 modules.getModule().add( classpathModule ); 526 } 527 else 528 { 529 classpathModule = null; 530 } 531 } 532 533 final ModelValidationReport validationReport = context.validateModel( 534 this.getModel(), new JAXBSource( marshaller, new ObjectFactory().createModules( modules ) ) ); 535 536 this.logValidationReport( context, validationReport ); 537 538 if ( !validationReport.isModelValid() ) 539 { 540 throw new ModelException( Messages.getMessage( "invalidModel", this.getModel() ) ); 541 } 542 543 if ( classpathModule != null ) 544 { 545 modules.getModule().remove( classpathModule ); 546 } 547 548 Module mergedModule = modules.getMergedModule( this.getModuleName() ); 549 mergedModule.setVendor( this.getModuleVendor() ); 550 mergedModule.setVersion( this.getModuleVersion() ); 551 552 for ( int i = 0, s0 = this.getModelObjectStylesheetResources().size(); i < s0; i++ ) 553 { 554 final Transformer transformer = 555 this.getTransformer( this.getModelObjectStylesheetResources().get( i ) ); 556 557 if ( transformer != null ) 558 { 559 final JAXBSource source = 560 new JAXBSource( marshaller, new ObjectFactory().createModule( mergedModule ) ); 561 562 final JAXBResult result = new JAXBResult( unmarshaller ); 563 transformer.transform( source, result ); 564 565 if ( result.getResult() instanceof JAXBElement<?> 566 && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Module ) 567 { 568 mergedModule = (Module) ( (JAXBElement<?>) result.getResult() ).getValue(); 569 } 570 else 571 { 572 throw new BuildException( Messages.getMessage( 573 "illegalTransformationResult", 574 this.getModelObjectStylesheetResources().get( i ).getLocation() ), this.getLocation() ); 575 576 } 577 } 578 } 579 580 this.log( Messages.getMessage( "writingEncoded", this.getModuleFile().getAbsolutePath(), 581 this.getModuleEncoding() ) ); 582 583 marshaller.setProperty( Marshaller.JAXB_ENCODING, this.getModuleEncoding() ); 584 marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE ); 585 marshaller.setSchema( context.createSchema( this.getModel() ) ); 586 marshaller.marshal( new ObjectFactory().createModule( mergedModule ), this.getModuleFile() ); 587 suppressExceptionOnClose = false; 588 } 589 catch ( final URISyntaxException e ) 590 { 591 throw new BuildException( Messages.getMessage( e ), e, this.getLocation() ); 592 } 593 catch ( final JAXBException e ) 594 { 595 String message = Messages.getMessage( e ); 596 if ( message == null ) 597 { 598 message = Messages.getMessage( e.getLinkedException() ); 599 } 600 601 throw new BuildException( message, e, this.getLocation() ); 602 } 603 catch ( final TransformerConfigurationException e ) 604 { 605 throw new BuildException( Messages.getMessage( e ), e, this.getLocation() ); 606 } 607 catch ( final TransformerException e ) 608 { 609 throw new BuildException( Messages.getMessage( e ), e, this.getLocation() ); 610 } 611 catch ( final ModelException e ) 612 { 613 throw new BuildException( Messages.getMessage( e ), e, this.getLocation() ); 614 } 615 finally 616 { 617 try 618 { 619 if ( classLoader != null ) 620 { 621 classLoader.close(); 622 } 623 } 624 catch ( final IOException e ) 625 { 626 if ( suppressExceptionOnClose ) 627 { 628 this.logMessage( Level.SEVERE, Messages.getMessage( e ), e ); 629 } 630 else 631 { 632 throw new BuildException( Messages.getMessage( e ), e, this.getLocation() ); 633 } 634 } 635 } 636 } 637 638 /** 639 * Tests inclusion of a given module based on property {@code moduleIncludes}. 640 * 641 * @param module The module to test. 642 * 643 * @return {@code true}, if {@code module} is included based on property {@code moduleIncludes}. 644 * 645 * @throws NullPointerException if {@code module} is {@code null}. 646 * 647 * @see #getModuleIncludes() 648 */ 649 public boolean isModuleIncluded( final Module module ) 650 { 651 if ( module == null ) 652 { 653 throw new NullPointerException( "module" ); 654 } 655 656 for ( NameType include : this.getModuleIncludes() ) 657 { 658 if ( include.getName().equals( module.getName() ) ) 659 { 660 return true; 661 } 662 } 663 664 return this.getModuleIncludes().isEmpty() ? true : false; 665 } 666 667 /** 668 * Tests exclusion of a given module based on property {@code moduleExcludes}. 669 * 670 * @param module The module to test. 671 * 672 * @return {@code true}, if {@code module} is excluded based on property {@code moduleExcludes}. 673 * 674 * @throws NullPointerException if {@code module} is {@code null}. 675 * 676 * @see #getModuleExcludes() 677 */ 678 public boolean isModuleExcluded( final Module module ) 679 { 680 if ( module == null ) 681 { 682 throw new NullPointerException( "module" ); 683 } 684 685 for ( NameType exclude : this.getModuleExcludes() ) 686 { 687 if ( exclude.getName().equals( module.getName() ) ) 688 { 689 return true; 690 } 691 } 692 693 return false; 694 } 695 696 /** {@inheritDoc} */ 697 @Override 698 public MergeModulesTask clone() 699 { 700 final MergeModulesTask clone = (MergeModulesTask) super.clone(); 701 clone.moduleFile = this.moduleFile != null ? new File( this.moduleFile.getAbsolutePath() ) : null; 702 703 if ( this.moduleExcludes != null ) 704 { 705 clone.moduleExcludes = new HashSet<NameType>( this.moduleExcludes.size() ); 706 for ( NameType e : this.moduleExcludes ) 707 { 708 clone.moduleExcludes.add( e.clone() ); 709 } 710 } 711 712 if ( this.moduleIncludes != null ) 713 { 714 clone.moduleIncludes = new HashSet<NameType>( this.moduleIncludes.size() ); 715 for ( NameType e : this.moduleIncludes ) 716 { 717 clone.moduleIncludes.add( e.clone() ); 718 } 719 } 720 721 if ( this.modelObjectStylesheetResources != null ) 722 { 723 clone.modelObjectStylesheetResources = 724 new ArrayList<TransformerResourceType>( this.modelObjectStylesheetResources.size() ); 725 726 for ( TransformerResourceType e : this.modelObjectStylesheetResources ) 727 { 728 clone.modelObjectStylesheetResources.add( e.clone() ); 729 } 730 } 731 732 return clone; 733 } 734 735 }