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