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