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