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