View Javadoc

1   /*
2    *   Copyright (C) Christian Schulte, 2005-206
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: JomcTask.java 4613 2012-09-22 10:07:08Z schulte $
29   *
30   */
31  package org.jomc.ant;
32  
33  import java.io.BufferedReader;
34  import java.io.File;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.StringReader;
38  import java.io.StringWriter;
39  import java.net.MalformedURLException;
40  import java.net.SocketTimeoutException;
41  import java.net.URI;
42  import java.net.URISyntaxException;
43  import java.net.URL;
44  import java.net.URLConnection;
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.Enumeration;
48  import java.util.HashSet;
49  import java.util.Iterator;
50  import java.util.LinkedList;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Properties;
54  import java.util.Set;
55  import java.util.logging.Level;
56  import javax.xml.bind.JAXBException;
57  import javax.xml.bind.Marshaller;
58  import javax.xml.transform.ErrorListener;
59  import javax.xml.transform.Transformer;
60  import javax.xml.transform.TransformerConfigurationException;
61  import javax.xml.transform.TransformerException;
62  import javax.xml.transform.TransformerFactory;
63  import javax.xml.transform.stream.StreamSource;
64  import org.apache.tools.ant.BuildException;
65  import org.apache.tools.ant.Project;
66  import org.apache.tools.ant.PropertyHelper;
67  import org.apache.tools.ant.Task;
68  import org.apache.tools.ant.types.Path;
69  import org.apache.tools.ant.types.Reference;
70  import org.jomc.ant.types.KeyValueType;
71  import org.jomc.ant.types.NameType;
72  import org.jomc.ant.types.PropertiesFormatType;
73  import org.jomc.ant.types.PropertiesResourceType;
74  import org.jomc.ant.types.ResourceType;
75  import org.jomc.ant.types.TransformerResourceType;
76  import org.jomc.model.ModelObject;
77  import org.jomc.modlet.DefaultModelContext;
78  import org.jomc.modlet.DefaultModletProvider;
79  import org.jomc.modlet.Model;
80  import org.jomc.modlet.ModelContext;
81  import org.jomc.modlet.ModelContextFactory;
82  import org.jomc.modlet.ModelException;
83  import org.jomc.modlet.ModelValidationReport;
84  import org.jomc.modlet.ModletProvider;
85  
86  /**
87   * Base class for executing tasks.
88   *
89   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
90   * @version $JOMC: JomcTask.java 4613 2012-09-22 10:07:08Z schulte $
91   * @see #execute()
92   */
93  public class JomcTask extends Task
94  {
95  
96      /** The class path to process. */
97      private Path classpath;
98  
99      /** The identifier of the model to process. */
100     private String model;
101 
102     /** {@code ModelContext} attributes to apply. */
103     private List<KeyValueType> modelContextAttributes;
104 
105     /** The name of the {@code ModelContextFactory} implementation class backing the task. */
106     private String modelContextFactoryClassName;
107 
108     /** Controls processing of models. */
109     private boolean modelProcessingEnabled = true;
110 
111     /** The location to search for modlets. */
112     private String modletLocation;
113 
114     /** The {@code http://jomc.org/modlet" target="alexandria_uri">http://jomc.org/modlet} namespace schema system id of the context backing the task. */
115     private String modletSchemaSystemId;
116 
117     /** The location to search for providers. */
118     private String providerLocation;
119 
120     /** The location to search for platform providers. */
121     private String platformProviderLocation;
122 
123     /** The global transformation parameters to apply. */
124     private List<KeyValueType> transformationParameters;
125 
126     /** The global transformation parameter resources to apply. */
127     private List<PropertiesResourceType> transformationParameterResources;
128 
129     /** The global transformation output properties to apply. */
130     private List<KeyValueType> transformationOutputProperties;
131 
132     /** The flag indicating JAXP schema validation of modlet resources is enabled. */
133     private boolean modletResourceValidationEnabled = true;
134 
135     /** Property controlling the execution of the task. */
136     private Object _if;
137 
138     /** Property controlling the execution of the task. */
139     private Object unless;
140 
141     /** Creates a new {@code JomcTask} instance. */
142     public JomcTask()
143     {
144         super();
145     }
146 
147     /**
148      * Gets an object controlling the execution of the task.
149      *
150      * @return An object controlling the execution of the task or {@code null}.
151      *
152      * @see #setIf(java.lang.Object)
153      */
154     public final Object getIf()
155     {
156         return this._if;
157     }
158 
159     /**
160      * Sets an object controlling the execution of the task.
161      *
162      * @param value The new object controlling the execution of the task or {@code null}.
163      *
164      * @see #getIf()
165      */
166     public final void setIf( final Object value )
167     {
168         this._if = value;
169     }
170 
171     /**
172      * Gets an object controlling the execution of the task.
173      *
174      * @return An object controlling the execution of the task or {@code null}.
175      *
176      * @see #setUnless(java.lang.Object)
177      */
178     public final Object getUnless()
179     {
180         if ( this.unless == null )
181         {
182             this.unless = Boolean.TRUE;
183         }
184 
185         return this.unless;
186     }
187 
188     /**
189      * Sets an object controlling the execution of the task.
190      *
191      * @param value The new object controlling the execution of the task or {@code null}.
192      *
193      * @see #getUnless()
194      */
195     public final void setUnless( final Object value )
196     {
197         this.unless = value;
198     }
199 
200     /**
201      * Creates a new {@code classpath} element instance.
202      *
203      * @return A new {@code classpath} element instance.
204      */
205     public final Path createClasspath()
206     {
207         return this.getClasspath().createPath();
208     }
209 
210     /**
211      * Gets the class path to process.
212      *
213      * @return The class path to process.
214      *
215      * @see #setClasspath(org.apache.tools.ant.types.Path)
216      */
217     public final Path getClasspath()
218     {
219         if ( this.classpath == null )
220         {
221             this.classpath = new Path( this.getProject() );
222         }
223 
224         return this.classpath;
225     }
226 
227     /**
228      * Adds to the class path to process.
229      *
230      * @param value The path to add to the list of class path elements.
231      *
232      * @see #getClasspath()
233      */
234     public final void setClasspath( final Path value )
235     {
236         this.getClasspath().add( value );
237     }
238 
239     /**
240      * Adds a reference to a class path defined elsewhere.
241      *
242      * @param value A reference to a class path.
243      *
244      * @see #getClasspath()
245      */
246     public final void setClasspathRef( final Reference value )
247     {
248         this.getClasspath().setRefid( value );
249     }
250 
251     /**
252      * Gets the identifier of the model to process.
253      *
254      * @return The identifier of the model to process.
255      *
256      * @see #setModel(java.lang.String)
257      */
258     public final String getModel()
259     {
260         if ( this.model == null )
261         {
262             this.model = ModelObject.MODEL_PUBLIC_ID;
263         }
264 
265         return this.model;
266     }
267 
268     /**
269      * Sets the identifier of the model to process.
270      *
271      * @param value The new identifier of the model to process or {@code null}.
272      *
273      * @see #getModel()
274      */
275     public final void setModel( final String value )
276     {
277         this.model = value;
278     }
279 
280     /**
281      * Gets the {@code ModelContext} attributes to apply.
282      * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
283      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
284      * model context attributes property.</p>
285      *
286      * @return The  {@code ModelContext} attributes to apply.
287      *
288      * @see #createModelContextAttribute()
289      * @see #newModelContext(java.lang.ClassLoader)
290      */
291     public final List<KeyValueType> getModelContextAttributes()
292     {
293         if ( this.modelContextAttributes == null )
294         {
295             this.modelContextAttributes = new LinkedList<KeyValueType>();
296         }
297 
298         return this.modelContextAttributes;
299     }
300 
301     /**
302      * Creates a new {@code modelContextAttribute} element instance.
303      *
304      * @return A new {@code modelContextAttribute} element instance.
305      *
306      * @see #getModelContextAttributes()
307      */
308     public KeyValueType createModelContextAttribute()
309     {
310         final KeyValueType modelContextAttribute = new KeyValueType();
311         this.getModelContextAttributes().add( modelContextAttribute );
312         return modelContextAttribute;
313     }
314 
315     /**
316      * Gets the name of the {@code ModelContextFactory} implementation class backing the task.
317      *
318      * @return The name of the {@code ModelContextFactory} implementation class backing the task or {@code null}.
319      *
320      * @see #setModelContextFactoryClassName(java.lang.String)
321      */
322     public final String getModelContextFactoryClassName()
323     {
324         return this.modelContextFactoryClassName;
325     }
326 
327     /**
328      * Sets the name of the {@code ModelContextFactory} implementation class backing the task.
329      *
330      * @param value The new name of the {@code ModelContextFactory} implementation class backing the task or
331      * {@code null}.
332      *
333      * @see #getModelContextFactoryClassName()
334      */
335     public final void setModelContextFactoryClassName( final String value )
336     {
337         this.modelContextFactoryClassName = value;
338     }
339 
340     /**
341      * Gets a flag indicating the processing of models is enabled.
342      *
343      * @return {@code true}, if processing of models is enabled; {@code false}, else.
344      *
345      * @see #setModelProcessingEnabled(boolean)
346      */
347     public final boolean isModelProcessingEnabled()
348     {
349         return this.modelProcessingEnabled;
350     }
351 
352     /**
353      * Sets the flag indicating the processing of models is enabled.
354      *
355      * @param value {@code true}, to enable processing of models; {@code false}, to disable processing of models.
356      *
357      * @see #isModelProcessingEnabled()
358      */
359     public final void setModelProcessingEnabled( final boolean value )
360     {
361         this.modelProcessingEnabled = value;
362     }
363 
364     /**
365      * Gets the location searched for modlets.
366      *
367      * @return The location searched for modlets or {@code null}.
368      *
369      * @see #setModletLocation(java.lang.String)
370      */
371     public final String getModletLocation()
372     {
373         return this.modletLocation;
374     }
375 
376     /**
377      * Sets the location to search for modlets.
378      *
379      * @param value The new location to search for modlets or {@code null}.
380      *
381      * @see #getModletLocation()
382      */
383     public final void setModletLocation( final String value )
384     {
385         this.modletLocation = value;
386     }
387 
388     /**
389      * Gets the {@code http://jomc.org/modlet} namespace schema system id of the context backing the task.
390      *
391      * @return The {@code http://jomc.org/modlet} namespace schema system id of the context backing the task or
392      * {@code null}.
393      *
394      * @see #setModletSchemaSystemId(java.lang.String)
395      */
396     public final String getModletSchemaSystemId()
397     {
398         return this.modletSchemaSystemId;
399     }
400 
401     /**
402      * Sets the {@code http://jomc.org/modlet} namespace schema system id of the context backing the task.
403      *
404      * @param value The new {@code http://jomc.org/modlet} namespace schema system id of the context backing the task or
405      * {@code null}.
406      *
407      * @see #getModletSchemaSystemId()
408      */
409     public final void setModletSchemaSystemId( final String value )
410     {
411         this.modletSchemaSystemId = value;
412     }
413 
414     /**
415      * Gets the location searched for providers.
416      *
417      * @return The location searched for providers or {@code null}.
418      *
419      * @see #setProviderLocation(java.lang.String)
420      */
421     public final String getProviderLocation()
422     {
423         return this.providerLocation;
424     }
425 
426     /**
427      * Sets the location to search for providers.
428      *
429      * @param value The new location to search for providers or {@code null}.
430      *
431      * @see #getProviderLocation()
432      */
433     public final void setProviderLocation( final String value )
434     {
435         this.providerLocation = value;
436     }
437 
438     /**
439      * Gets the location searched for platform provider resources.
440      *
441      * @return The location searched for platform provider resources or {@code null}.
442      *
443      * @see #setPlatformProviderLocation(java.lang.String)
444      */
445     public final String getPlatformProviderLocation()
446     {
447         return this.platformProviderLocation;
448     }
449 
450     /**
451      * Sets the location to search for platform provider resources.
452      *
453      * @param value The new location to search for platform provider resources or {@code null}.
454      *
455      * @see #getPlatformProviderLocation()
456      */
457     public final void setPlatformProviderLocation( final String value )
458     {
459         this.platformProviderLocation = value;
460     }
461 
462     /**
463      * Gets the global transformation parameters to apply.
464      * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
465      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
466      * transformation parameters property.</p>
467      *
468      * @return The global transformation parameters to apply.
469      *
470      * @see #createTransformationParameter()
471      * @see #getTransformer(org.jomc.ant.types.TransformerResourceType)
472      */
473     public final List<KeyValueType> getTransformationParameters()
474     {
475         if ( this.transformationParameters == null )
476         {
477             this.transformationParameters = new LinkedList<KeyValueType>();
478         }
479 
480         return this.transformationParameters;
481     }
482 
483     /**
484      * Creates a new {@code transformationParameter} element instance.
485      *
486      * @return A new {@code transformationParameter} element instance.
487      *
488      * @see #getTransformationParameters()
489      */
490     public KeyValueType createTransformationParameter()
491     {
492         final KeyValueType transformationParameter = new KeyValueType();
493         this.getTransformationParameters().add( transformationParameter );
494         return transformationParameter;
495     }
496 
497     /**
498      * Gets the global transformation parameter resources to apply.
499      * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
500      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
501      * transformation parameter resources property.</p>
502      *
503      * @return The global transformation parameter resources to apply.
504      *
505      * @see #createTransformationParameterResource()
506      * @see #getTransformer(org.jomc.ant.types.TransformerResourceType)
507      */
508     public final List<PropertiesResourceType> getTransformationParameterResources()
509     {
510         if ( this.transformationParameterResources == null )
511         {
512             this.transformationParameterResources = new LinkedList<PropertiesResourceType>();
513         }
514 
515         return this.transformationParameterResources;
516     }
517 
518     /**
519      * Creates a new {@code transformationParameterResource} element instance.
520      *
521      * @return A new {@code transformationParameterResource} element instance.
522      *
523      * @see #getTransformationParameterResources()
524      */
525     public PropertiesResourceType createTransformationParameterResource()
526     {
527         final PropertiesResourceType transformationParameterResource = new PropertiesResourceType();
528         this.getTransformationParameterResources().add( transformationParameterResource );
529         return transformationParameterResource;
530     }
531 
532     /**
533      * Gets the global transformation output properties to apply.
534      * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
535      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
536      * transformation output properties property.</p>
537      *
538      * @return The global transformation output properties to apply.
539      *
540      * @see #createTransformationOutputProperty()
541      */
542     public final List<KeyValueType> getTransformationOutputProperties()
543     {
544         if ( this.transformationOutputProperties == null )
545         {
546             this.transformationOutputProperties = new LinkedList<KeyValueType>();
547         }
548 
549         return this.transformationOutputProperties;
550     }
551 
552     /**
553      * Creates a new {@code transformationOutputProperty} element instance.
554      *
555      * @return A new {@code transformationOutputProperty} element instance.
556      *
557      * @see #getTransformationOutputProperties()
558      */
559     public KeyValueType createTransformationOutputProperty()
560     {
561         final KeyValueType transformationOutputProperty = new KeyValueType();
562         this.getTransformationOutputProperties().add( transformationOutputProperty );
563         return transformationOutputProperty;
564     }
565 
566     /**
567      * Gets a flag indicating JAXP schema validation of modlet resources is enabled.
568      *
569      * @return {@code true}, if JAXP schema validation of modlet resources is enabled; {@code false}, else.
570      *
571      * @see #setModletResourceValidationEnabled(boolean)
572      */
573     public final boolean isModletResourceValidationEnabled()
574     {
575         return this.modletResourceValidationEnabled;
576     }
577 
578     /**
579      * Sets the flag indicating JAXP schema validation of modlet resources is enabled.
580      *
581      * @param value {@code true}, to enable JAXP schema validation of modlet resources; {@code false}, to disable JAXP
582      * schema validation of modlet resources.
583      *
584      * @see #isModletResourceValidationEnabled()
585      */
586     public final void setModletResourceValidationEnabled( final boolean value )
587     {
588         this.modletResourceValidationEnabled = value;
589     }
590 
591     /**
592      * Called by the project to let the task do its work.
593      *
594      * @throws BuildException if execution fails.
595      *
596      * @see #getIf()
597      * @see #getUnless()
598      * @see #preExecuteTask()
599      * @see #executeTask()
600      * @see #postExecuteTask()
601      */
602     @Override
603     public final void execute() throws BuildException
604     {
605         final PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper( this.getProject() );
606 
607         if ( propertyHelper.testIfCondition( this.getIf() ) && !propertyHelper.testUnlessCondition( this.getUnless() ) )
608         {
609             try
610             {
611                 this.preExecuteTask();
612                 this.executeTask();
613             }
614             finally
615             {
616                 this.postExecuteTask();
617             }
618         }
619     }
620 
621     /**
622      * Called by the {@code execute} method prior to the {@code executeTask} method.
623      *
624      * @throws BuildException if execution fails.
625      *
626      * @see #execute()
627      */
628     public void preExecuteTask() throws BuildException
629     {
630         this.logSeparator();
631         this.log( Messages.getMessage( "title" ) );
632         this.logSeparator();
633 
634         this.assertNotNull( "model", this.getModel() );
635         this.assertKeysNotNull( this.getModelContextAttributes() );
636         this.assertKeysNotNull( this.getTransformationParameters() );
637         this.assertKeysNotNull( this.getTransformationOutputProperties() );
638         this.assertLocationsNotNull( this.getTransformationParameterResources() );
639     }
640 
641     /**
642      * Called by the {@code execute} method prior to the {@code postExecuteTask} method.
643      *
644      * @throws BuildException if execution fails.
645      *
646      * @see #execute()
647      */
648     public void executeTask() throws BuildException
649     {
650         this.getProject().log( Messages.getMessage( "unimplementedTask", this.getClass().getName(), "executeTask" ),
651                                Project.MSG_WARN );
652 
653     }
654 
655     /**
656      * Called by the {@code execute} method after the {@code preExecuteTask}/{@code executeTask} methods even if those
657      * methods threw an exception.
658      *
659      * @throws BuildException if execution fails.
660      *
661      * @see #execute()
662      */
663     public void postExecuteTask() throws BuildException
664     {
665         this.logSeparator();
666     }
667 
668     /**
669      * Gets a {@code Model} from a given {@code ModelContext}.
670      *
671      * @param context The context to get a {@code Model} from.
672      *
673      * @return The {@code Model} from {@code context}.
674      *
675      * @throws NullPointerException if {@code contexŧ} is {@code null}.
676      * @throws ModelException if getting the model fails.
677      *
678      * @see #getModel()
679      * @see #isModelProcessingEnabled()
680      */
681     public Model getModel( final ModelContext context ) throws ModelException
682     {
683         if ( context == null )
684         {
685             throw new NullPointerException( "context" );
686         }
687 
688         Model foundModel = context.findModel( this.getModel() );
689 
690         if ( foundModel != null && this.isModelProcessingEnabled() )
691         {
692             foundModel = context.processModel( foundModel );
693         }
694 
695         return foundModel;
696     }
697 
698     /**
699      * Creates an {@code URL} for a given resource location.
700      * <p>This method first searches the class path of the task for a single resource matching {@code location}. If
701      * such a resource is found, the URL of that resource is returned. If no such resource is found, an attempt is made
702      * to parse the given location to an URL. On successful parsing, that URL is returned. Failing that, the given
703      * location is interpreted as a file name relative to the project's base directory. If that file is found, the URL
704      * of that file is returned. Otherwise {@code null} is returned.</p>
705      *
706      * @param location The resource location to create an {@code URL} from.
707      *
708      * @return An {@code URL} for {@code location} or {@code null}, if parsing {@code location} to an URL fails and
709      * {@code location} points to a non-existent resource.
710      *
711      * @throws NullPointerException if {@code location} is {@code null}.
712      * @throws BuildException if creating an URL fails.
713      */
714     public URL getResource( final String location ) throws BuildException
715     {
716         if ( location == null )
717         {
718             throw new NullPointerException( "location" );
719         }
720 
721         try
722         {
723             String absolute = location;
724             if ( !absolute.startsWith( "/" ) )
725             {
726                 absolute = "/" + absolute;
727             }
728 
729             URL resource = this.getClass().getResource( absolute );
730             if ( resource == null )
731             {
732                 try
733                 {
734                     resource = new URL( location );
735                 }
736                 catch ( final MalformedURLException e )
737                 {
738                     this.log( e, Project.MSG_DEBUG );
739                     resource = null;
740                 }
741             }
742 
743             if ( resource == null )
744             {
745                 final File f = this.getProject().resolveFile( location );
746 
747                 if ( f.isFile() )
748                 {
749                     resource = f.toURI().toURL();
750                 }
751             }
752 
753             return resource;
754         }
755         catch ( final MalformedURLException e )
756         {
757             String m = Messages.getMessage( e );
758             m = m == null ? "" : " " + m;
759 
760             throw new BuildException( Messages.getMessage( "malformedLocation", location, m ), e, this.getLocation() );
761         }
762     }
763 
764     /**
765      * Creates an array of {@code URL}s for a given resource location.
766      * <p>This method first searches the given context for resources matching {@code location}. If such resources are
767      * found, an array of URLs of those resources is returned. If no such resources are found, an attempt is made
768      * to parse the given location to an URL. On successful parsing, that URL is returned. Failing that, the given
769      * location is interpreted as a file name relative to the project's base directory. If that file is found, the URL
770      * of that file is returned. Otherwise an empty array is returned.</p>
771      *
772      * @param context The context to search for resources.
773      * @param location The resource location to create an array of {@code URL}s from.
774      *
775      * @return An array of {@code URL}s for {@code location} or an empty array if parsing {@code location} to an URL
776      * fails and {@code location} points to non-existent resources.
777      *
778      * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
779      * @throws BuildException if creating an URL array fails.
780      */
781     public URL[] getResources( final ModelContext context, final String location ) throws BuildException
782     {
783         if ( context == null )
784         {
785             throw new NullPointerException( "context" );
786         }
787         if ( location == null )
788         {
789             throw new NullPointerException( "location" );
790         }
791 
792         final Set<URI> uris = new HashSet<URI>();
793 
794         try
795         {
796             for ( final Enumeration<URL> e = context.findResources( location ); e.hasMoreElements(); )
797             {
798                 uris.add( e.nextElement().toURI() );
799             }
800         }
801         catch ( final URISyntaxException e )
802         {
803             this.log( e, Project.MSG_DEBUG );
804         }
805         catch ( final ModelException e )
806         {
807             this.log( e, Project.MSG_DEBUG );
808         }
809 
810         if ( uris.isEmpty() )
811         {
812             try
813             {
814                 uris.add( new URL( location ).toURI() );
815             }
816             catch ( final MalformedURLException e )
817             {
818                 this.log( e, Project.MSG_DEBUG );
819             }
820             catch ( final URISyntaxException e )
821             {
822                 this.log( e, Project.MSG_DEBUG );
823             }
824         }
825 
826         if ( uris.isEmpty() )
827         {
828             final File f = this.getProject().resolveFile( location );
829 
830             if ( f.isFile() )
831             {
832                 uris.add( f.toURI() );
833             }
834         }
835 
836         int i = 0;
837         final URL[] urls = new URL[ uris.size() ];
838 
839         for ( URI uri : uris )
840         {
841             try
842             {
843                 urls[i++] = uri.toURL();
844             }
845             catch ( final MalformedURLException e )
846             {
847                 String m = Messages.getMessage( e );
848                 m = m == null ? "" : " " + m;
849 
850                 throw new BuildException( Messages.getMessage( "malformedLocation", uri.toASCIIString(), m ), e,
851                                           this.getLocation() );
852 
853             }
854         }
855 
856         return urls;
857     }
858 
859     /**
860      * Creates an {@code URL} for a given directory location.
861      * <p>This method first attempts to parse the given location to an URL. On successful parsing, that URL is returned.
862      * Failing that, the given location is interpreted as a directory name relative to the project's base directory. If
863      * that directory is found, the URL of that directory is returned. Otherwise {@code null} is returned.</p>
864      *
865      * @param location The directory location to create an {@code URL} from.
866      *
867      * @return An {@code URL} for {@code location} or {@code null}, if parsing {@code location} to an URL fails and
868      * {@code location} points to a non-existent directory.
869      *
870      * @throws NullPointerException if {@code location} is {@code null}.
871      * @throws BuildException if creating an URL fails.
872      */
873     public URL getDirectory( final String location ) throws BuildException
874     {
875         if ( location == null )
876         {
877             throw new NullPointerException( "location" );
878         }
879 
880         try
881         {
882             URL resource = null;
883 
884             try
885             {
886                 resource = new URL( location );
887             }
888             catch ( final MalformedURLException e )
889             {
890                 this.log( e, Project.MSG_DEBUG );
891                 resource = null;
892             }
893 
894             if ( resource == null )
895             {
896                 final File f = this.getProject().resolveFile( location );
897 
898                 if ( f.isDirectory() )
899                 {
900                     resource = f.toURI().toURL();
901                 }
902             }
903 
904             return resource;
905         }
906         catch ( final MalformedURLException e )
907         {
908             String m = Messages.getMessage( e );
909             m = m == null ? "" : " " + m;
910 
911             throw new BuildException( Messages.getMessage( "malformedLocation", location, m ), e, this.getLocation() );
912         }
913     }
914 
915     /**
916      * Creates a new {@code Transformer} for a given {@code TransformerResourceType}.
917      *
918      * @param resource The resource to create a {@code Transformer} of.
919      *
920      * @return A new {@code Transformer} for {@code resource} or {@code null}, if {@code resource} is not found and
921      * flagged optional.
922      *
923      * @throws TransformerConfigurationException if creating a new {@code Transformer} fails.
924      *
925      * @see #getTransformationParameterResources()
926      * @see #getTransformationParameters()
927      * @see #getResource(java.lang.String)
928      */
929     public Transformer getTransformer( final TransformerResourceType resource ) throws TransformerConfigurationException
930     {
931         if ( resource == null )
932         {
933             throw new NullPointerException( "resource" );
934         }
935 
936         InputStream in = null;
937         boolean suppressExceptionOnClose = true;
938         final URL url = this.getResource( resource.getLocation() );
939 
940         try
941         {
942             if ( url != null )
943             {
944                 final ErrorListener errorListener = new ErrorListener()
945                 {
946 
947                     public void warning( final TransformerException exception ) throws TransformerException
948                     {
949                         if ( getProject() != null )
950                         {
951                             getProject().log( Messages.getMessage( exception ), exception, Project.MSG_WARN );
952                         }
953                     }
954 
955                     public void error( final TransformerException exception ) throws TransformerException
956                     {
957                         throw exception;
958                     }
959 
960                     public void fatalError( final TransformerException exception ) throws TransformerException
961                     {
962                         throw exception;
963                     }
964 
965                 };
966 
967                 final URLConnection con = url.openConnection();
968                 con.setConnectTimeout( resource.getConnectTimeout() );
969                 con.setReadTimeout( resource.getReadTimeout() );
970                 con.connect();
971                 in = con.getInputStream();
972 
973                 final TransformerFactory f = TransformerFactory.newInstance();
974                 f.setErrorListener( errorListener );
975                 final Transformer transformer = f.newTransformer( new StreamSource( in, url.toURI().toASCIIString() ) );
976                 transformer.setErrorListener( errorListener );
977 
978                 for ( Map.Entry<Object, Object> e : System.getProperties().entrySet() )
979                 {
980                     transformer.setParameter( e.getKey().toString(), e.getValue() );
981                 }
982 
983                 for ( final Iterator<Map.Entry<?, ?>> it = this.getProject().getProperties().entrySet().iterator();
984                       it.hasNext(); )
985                 {
986                     final Map.Entry<?, ?> e = it.next();
987                     transformer.setParameter( e.getKey().toString(), e.getValue() );
988                 }
989 
990                 for ( int i = 0, s0 = this.getTransformationParameterResources().size(); i < s0; i++ )
991                 {
992                     for ( Map.Entry<Object, Object> e :
993                           this.getProperties( this.getTransformationParameterResources().get( i ) ).entrySet() )
994                     {
995                         transformer.setParameter( e.getKey().toString(), e.getValue() );
996                     }
997                 }
998 
999                 for ( int i = 0, s0 = this.getTransformationParameters().size(); i < s0; i++ )
1000                 {
1001                     final KeyValueType p = this.getTransformationParameters().get( i );
1002                     transformer.setParameter( p.getKey(), p.getObject( this.getLocation() ) );
1003                 }
1004 
1005                 for ( int i = 0, s0 = this.getTransformationOutputProperties().size(); i < s0; i++ )
1006                 {
1007                     final KeyValueType p = this.getTransformationOutputProperties().get( i );
1008                     transformer.setOutputProperty( p.getKey(), p.getValue() );
1009                 }
1010 
1011                 for ( int i = 0, s0 = resource.getTransformationParameterResources().size(); i < s0; i++ )
1012                 {
1013                     for ( Map.Entry<Object, Object> e :
1014                           this.getProperties( resource.getTransformationParameterResources().get( i ) ).entrySet() )
1015                     {
1016                         transformer.setParameter( e.getKey().toString(), e.getValue() );
1017                     }
1018                 }
1019 
1020                 for ( int i = 0, s0 = resource.getTransformationParameters().size(); i < s0; i++ )
1021                 {
1022                     final KeyValueType p = resource.getTransformationParameters().get( i );
1023                     transformer.setParameter( p.getKey(), p.getObject( this.getLocation() ) );
1024                 }
1025 
1026                 for ( int i = 0, s0 = resource.getTransformationOutputProperties().size(); i < s0; i++ )
1027                 {
1028                     final KeyValueType p = resource.getTransformationOutputProperties().get( i );
1029                     transformer.setOutputProperty( p.getKey(), p.getValue() );
1030                 }
1031 
1032                 suppressExceptionOnClose = false;
1033                 return transformer;
1034             }
1035             else if ( resource.isOptional() )
1036             {
1037                 this.log( Messages.getMessage( "transformerNotFound", resource.getLocation() ), Project.MSG_WARN );
1038             }
1039             else
1040             {
1041                 throw new BuildException( Messages.getMessage( "transformerNotFound", resource.getLocation() ),
1042                                           this.getLocation() );
1043 
1044             }
1045         }
1046         catch ( final URISyntaxException e )
1047         {
1048             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1049         }
1050         catch ( final SocketTimeoutException e )
1051         {
1052             final String message = Messages.getMessage( e );
1053 
1054             if ( resource.isOptional() )
1055             {
1056                 this.getProject().log( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1057                                        e, Project.MSG_WARN );
1058 
1059             }
1060             else
1061             {
1062                 throw new BuildException( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1063                                           e, this.getLocation() );
1064 
1065             }
1066         }
1067         catch ( final IOException e )
1068         {
1069             final String message = Messages.getMessage( e );
1070 
1071             if ( resource.isOptional() )
1072             {
1073                 this.getProject().log( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1074                                        e, Project.MSG_WARN );
1075 
1076             }
1077             else
1078             {
1079                 throw new BuildException( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1080                                           e, this.getLocation() );
1081 
1082             }
1083         }
1084         finally
1085         {
1086             try
1087             {
1088                 if ( in != null )
1089                 {
1090                     in.close();
1091                 }
1092             }
1093             catch ( final IOException e )
1094             {
1095                 if ( suppressExceptionOnClose )
1096                 {
1097                     this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
1098                 }
1099                 else
1100                 {
1101                     throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1102                 }
1103             }
1104         }
1105 
1106         return null;
1107     }
1108 
1109     /**
1110      * Creates a new {@code Properties} instance from a {@code PropertiesResourceType}.
1111      *
1112      * @param propertiesResourceType The {@code PropertiesResourceType} specifying the properties to create.
1113      *
1114      * @return The properties for {@code propertiesResourceType}.
1115      *
1116      * @throws NullPointerException if {@code propertiesResourceType} is {@code null}.
1117      * @throws BuildException if loading properties fails.
1118      */
1119     public Properties getProperties( final PropertiesResourceType propertiesResourceType ) throws BuildException
1120     {
1121         if ( propertiesResourceType == null )
1122         {
1123             throw new NullPointerException( "propertiesResourceType" );
1124         }
1125 
1126         InputStream in = null;
1127         boolean suppressExceptionOnClose = true;
1128         final Properties properties = new Properties();
1129         final URL url = this.getResource( propertiesResourceType.getLocation() );
1130 
1131         try
1132         {
1133             if ( url != null )
1134             {
1135                 final URLConnection con = url.openConnection();
1136                 con.setConnectTimeout( propertiesResourceType.getConnectTimeout() );
1137                 con.setReadTimeout( propertiesResourceType.getReadTimeout() );
1138                 con.connect();
1139 
1140                 in = con.getInputStream();
1141 
1142                 if ( propertiesResourceType.getFormat() == PropertiesFormatType.PLAIN )
1143                 {
1144                     properties.load( in );
1145                 }
1146                 else if ( propertiesResourceType.getFormat() == PropertiesFormatType.XML )
1147                 {
1148                     properties.loadFromXML( in );
1149                 }
1150             }
1151             else if ( propertiesResourceType.isOptional() )
1152             {
1153                 this.log( Messages.getMessage( "propertiesNotFound", propertiesResourceType.getLocation() ),
1154                           Project.MSG_WARN );
1155 
1156             }
1157             else
1158             {
1159                 throw new BuildException( Messages.getMessage(
1160                     "propertiesNotFound", propertiesResourceType.getLocation() ), this.getLocation() );
1161 
1162             }
1163 
1164             suppressExceptionOnClose = false;
1165         }
1166         catch ( final SocketTimeoutException e )
1167         {
1168             final String message = Messages.getMessage( e );
1169 
1170             if ( propertiesResourceType.isOptional() )
1171             {
1172                 this.getProject().log( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1173                                        e, Project.MSG_WARN );
1174 
1175             }
1176             else
1177             {
1178                 throw new BuildException( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1179                                           e, this.getLocation() );
1180 
1181             }
1182         }
1183         catch ( final IOException e )
1184         {
1185             final String message = Messages.getMessage( e );
1186 
1187             if ( propertiesResourceType.isOptional() )
1188             {
1189                 this.getProject().log( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1190                                        e, Project.MSG_WARN );
1191 
1192             }
1193             else
1194             {
1195                 throw new BuildException( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1196                                           e, this.getLocation() );
1197 
1198             }
1199         }
1200         finally
1201         {
1202             try
1203             {
1204                 if ( in != null )
1205                 {
1206                     in.close();
1207                 }
1208             }
1209             catch ( final IOException e )
1210             {
1211                 if ( suppressExceptionOnClose )
1212                 {
1213                     this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
1214                 }
1215                 else
1216                 {
1217                     throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1218                 }
1219             }
1220         }
1221 
1222         return properties;
1223     }
1224 
1225     /**
1226      * Creates a new {@code ProjectClassLoader} instance.
1227      *
1228      * @return A new {@code ProjectClassLoader} instance.
1229      *
1230      * @throws BuildException if creating a new class loader instance fails.
1231      */
1232     public ProjectClassLoader newProjectClassLoader() throws BuildException
1233     {
1234         try
1235         {
1236             final ProjectClassLoader classLoader = new ProjectClassLoader( this.getProject(), this.getClasspath() );
1237             classLoader.getModletExcludes().addAll( ProjectClassLoader.getDefaultModletExcludes() );
1238             classLoader.getProviderExcludes().addAll( ProjectClassLoader.getDefaultProviderExcludes() );
1239             classLoader.getSchemaExcludes().addAll( ProjectClassLoader.getDefaultSchemaExcludes() );
1240             classLoader.getServiceExcludes().addAll( ProjectClassLoader.getDefaultServiceExcludes() );
1241 
1242             if ( this.getModletLocation() != null )
1243             {
1244                 classLoader.getModletResourceLocations().add( this.getModletLocation() );
1245             }
1246             else
1247             {
1248                 classLoader.getModletResourceLocations().add( DefaultModletProvider.getDefaultModletLocation() );
1249             }
1250 
1251             if ( this.getProviderLocation() != null )
1252             {
1253                 classLoader.getProviderResourceLocations().add(
1254                     this.getProviderLocation() + "/" + ModletProvider.class.getName() );
1255 
1256             }
1257             else
1258             {
1259                 classLoader.getProviderResourceLocations().add(
1260                     DefaultModelContext.getDefaultProviderLocation() + "/" + ModletProvider.class.getName() );
1261 
1262             }
1263 
1264             return classLoader;
1265         }
1266         catch ( final IOException e )
1267         {
1268             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1269         }
1270     }
1271 
1272     /**
1273      * Creates a new {@code ModelContext} instance using a given class loader.
1274      *
1275      * @param classLoader The class loader to create a new {@code ModelContext} instance with.
1276      *
1277      * @return A new {@code ModelContext} instance backed by {@code classLoader}.
1278      *
1279      * @throws ModelException if creating a new {@code ModelContext} instance fails.
1280      */
1281     public ModelContext newModelContext( final ClassLoader classLoader ) throws ModelException
1282     {
1283         final ModelContextFactory modelContextFactory;
1284         if ( this.modelContextFactoryClassName != null )
1285         {
1286             modelContextFactory = ModelContextFactory.newInstance( this.getModelContextFactoryClassName() );
1287         }
1288         else
1289         {
1290             modelContextFactory = ModelContextFactory.newInstance();
1291         }
1292 
1293         final ModelContext modelContext = modelContextFactory.newModelContext( classLoader );
1294         modelContext.setLogLevel( Level.ALL );
1295         modelContext.setModletSchemaSystemId( this.getModletSchemaSystemId() );
1296 
1297         modelContext.getListeners().add( new ModelContext.Listener()
1298         {
1299 
1300             @Override
1301             public void onLog( final Level level, final String message, final Throwable t )
1302             {
1303                 super.onLog( level, message, t );
1304                 logMessage( level, message, t );
1305             }
1306 
1307         } );
1308 
1309         if ( this.getProviderLocation() != null )
1310         {
1311             modelContext.setAttribute( DefaultModelContext.PROVIDER_LOCATION_ATTRIBUTE_NAME,
1312                                        this.getProviderLocation() );
1313 
1314         }
1315 
1316         if ( this.getPlatformProviderLocation() != null )
1317         {
1318             modelContext.setAttribute( DefaultModelContext.PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME,
1319                                        this.getPlatformProviderLocation() );
1320 
1321         }
1322 
1323         if ( this.getModletLocation() != null )
1324         {
1325             modelContext.setAttribute( DefaultModletProvider.MODLET_LOCATION_ATTRIBUTE_NAME, this.getModletLocation() );
1326         }
1327 
1328         modelContext.setAttribute( DefaultModletProvider.VALIDATING_ATTRIBUTE_NAME,
1329                                    this.isModletResourceValidationEnabled() );
1330 
1331         for ( int i = 0, s0 = this.getModelContextAttributes().size(); i < s0; i++ )
1332         {
1333             final KeyValueType kv = this.getModelContextAttributes().get( i );
1334             final Object object = kv.getObject( this.getLocation() );
1335 
1336             if ( object != null )
1337             {
1338                 modelContext.setAttribute( kv.getKey(), object );
1339             }
1340             else
1341             {
1342                 modelContext.clearAttribute( kv.getKey() );
1343             }
1344         }
1345 
1346         return modelContext;
1347     }
1348 
1349     /**
1350      * Throws a {@code BuildException} on a given {@code null} value.
1351      *
1352      * @param attributeName The name of a mandatory attribute.
1353      * @param value The value of that attribute.
1354      *
1355      * @throws NullPointerException if {@code attributeName} is {@code null}.
1356      * @throws BuildException if {@code value} is {@code null}.
1357      */
1358     public final void assertNotNull( final String attributeName, final Object value ) throws BuildException
1359     {
1360         if ( attributeName == null )
1361         {
1362             throw new NullPointerException( "attributeName" );
1363         }
1364 
1365         if ( value == null )
1366         {
1367             throw new BuildException( Messages.getMessage( "mandatoryAttribute", attributeName ), this.getLocation() );
1368         }
1369     }
1370 
1371     /**
1372      * Throws a {@code BuildException} on a {@code null} value of a {@code name} property of a given {@code NameType}
1373      * collection.
1374      *
1375      * @param names The collection holding the  {@code NameType} instances to test.
1376      *
1377      * @throws NullPointerException if {@code names} is {@code null}.
1378      * @throws BuildException if a {@code name} property of a given {@code NameType} from the {@code names} collection
1379      * holds a {@code null} value.
1380      */
1381     public final void assertNamesNotNull( final Collection<? extends NameType> names ) throws BuildException
1382     {
1383         if ( names == null )
1384         {
1385             throw new NullPointerException( "names" );
1386         }
1387 
1388         for ( NameType n : names )
1389         {
1390             this.assertNotNull( "name", n.getName() );
1391         }
1392     }
1393 
1394     /**
1395      * Throws a {@code BuildException} on a {@code null} value of a {@code key} property of a given {@code KeyValueType}
1396      * collection.
1397      *
1398      * @param keys The collection holding the  {@code KeyValueType} instances to test.
1399      *
1400      * @throws NullPointerException if {@code keys} is {@code null}.
1401      * @throws BuildException if a {@code key} property of a given {@code KeyValueType} from the {@code keys} collection
1402      * holds a {@code null} value.
1403      */
1404     public final void assertKeysNotNull( final Collection<? extends KeyValueType> keys ) throws BuildException
1405     {
1406         if ( keys == null )
1407         {
1408             throw new NullPointerException( "keys" );
1409         }
1410 
1411         for ( KeyValueType k : keys )
1412         {
1413             this.assertNotNull( "key", k.getKey() );
1414         }
1415     }
1416 
1417     /**
1418      * Throws a {@code BuildException} on a {@code null} value of a {@code location} property of a given
1419      * {@code ResourceType} collection.
1420      *
1421      * @param locations The collection holding the {@code ResourceType} instances to test.
1422      *
1423      * @throws NullPointerException if {@code locations} is {@code null}.
1424      * @throws BuildException if a {@code location} property of a given {@code ResourceType} from the {@code locations}
1425      * collection holds a {@code null} value.
1426      */
1427     public final void assertLocationsNotNull( final Collection<? extends ResourceType> locations )
1428         throws BuildException
1429     {
1430         if ( locations == null )
1431         {
1432             throw new NullPointerException( "locations" );
1433         }
1434 
1435         for ( ResourceType r : locations )
1436         {
1437             assertNotNull( "location", r.getLocation() );
1438 
1439             if ( r instanceof TransformerResourceType )
1440             {
1441                 assertKeysNotNull( ( (TransformerResourceType) r ).getTransformationParameters() );
1442                 assertLocationsNotNull( ( (TransformerResourceType) r ).getTransformationParameterResources() );
1443                 assertKeysNotNull( ( (TransformerResourceType) r ).getTransformationOutputProperties() );
1444             }
1445         }
1446     }
1447 
1448     /** Logs a separator string. */
1449     public final void logSeparator()
1450     {
1451         this.log( Messages.getMessage( "separator" ) );
1452     }
1453 
1454     /**
1455      * Logs a message at a given level.
1456      *
1457      * @param level The level to log at.
1458      * @param message The message to log.
1459      *
1460      * @throws BuildException if logging fails.
1461      */
1462     public final void logMessage( final Level level, final String message ) throws BuildException
1463     {
1464         BufferedReader reader = null;
1465         boolean suppressExceptionOnClose = true;
1466 
1467         try
1468         {
1469             String line = null;
1470             reader = new BufferedReader( new StringReader( message ) );
1471 
1472             while ( ( line = reader.readLine() ) != null )
1473             {
1474                 if ( level.intValue() >= Level.SEVERE.intValue() )
1475                 {
1476                     log( line, Project.MSG_ERR );
1477                 }
1478                 else if ( level.intValue() >= Level.WARNING.intValue() )
1479                 {
1480                     log( line, Project.MSG_WARN );
1481                 }
1482                 else if ( level.intValue() >= Level.INFO.intValue() )
1483                 {
1484                     log( line, Project.MSG_INFO );
1485                 }
1486                 else
1487                 {
1488                     log( line, Project.MSG_DEBUG );
1489                 }
1490             }
1491 
1492             suppressExceptionOnClose = false;
1493         }
1494         catch ( final IOException e )
1495         {
1496             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1497         }
1498         finally
1499         {
1500             try
1501             {
1502                 if ( reader != null )
1503                 {
1504                     reader.close();
1505                 }
1506             }
1507             catch ( final IOException e )
1508             {
1509                 if ( suppressExceptionOnClose )
1510                 {
1511                     this.log( e, Project.MSG_ERR );
1512                 }
1513                 else
1514                 {
1515                     throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1516                 }
1517             }
1518         }
1519     }
1520 
1521     /**
1522      * Logs a message at a given level.
1523      *
1524      * @param level The level to log at.
1525      * @param message The message to log.
1526      * @param throwable The throwable to log.
1527      *
1528      * @throws BuildException if logging fails.
1529      */
1530     public final void logMessage( final Level level, final String message, final Throwable throwable )
1531         throws BuildException
1532     {
1533         this.logMessage( level, message );
1534 
1535         if ( level.intValue() >= Level.SEVERE.intValue() )
1536         {
1537             log( throwable, Project.MSG_ERR );
1538         }
1539         else if ( level.intValue() >= Level.WARNING.intValue() )
1540         {
1541             log( throwable, Project.MSG_WARN );
1542         }
1543         else if ( level.intValue() >= Level.INFO.intValue() )
1544         {
1545             log( throwable, Project.MSG_INFO );
1546         }
1547         else
1548         {
1549             log( throwable, Project.MSG_DEBUG );
1550         }
1551     }
1552 
1553     /**
1554      * Logs a validation report.
1555      *
1556      * @param context The context to use for logging the report.
1557      * @param report The report to log.
1558      *
1559      * @throws NullPointerException if {@code context} or {@code report} is {@code null}.
1560      * @throws BuildException if logging fails.
1561      */
1562     public final void logValidationReport( final ModelContext context, final ModelValidationReport report )
1563     {
1564         try
1565         {
1566             if ( !report.getDetails().isEmpty() )
1567             {
1568                 this.logSeparator();
1569                 Marshaller marshaller = null;
1570 
1571                 for ( ModelValidationReport.Detail detail : report.getDetails() )
1572                 {
1573                     this.logMessage( detail.getLevel(), "o " + detail.getMessage() );
1574 
1575                     if ( detail.getElement() != null )
1576                     {
1577                         if ( marshaller == null )
1578                         {
1579                             marshaller = context.createMarshaller( this.getModel() );
1580                             marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
1581                         }
1582 
1583                         final StringWriter stringWriter = new StringWriter();
1584                         marshaller.marshal( detail.getElement(), stringWriter );
1585                         this.logMessage( Level.FINEST, stringWriter.toString() );
1586                     }
1587                 }
1588             }
1589         }
1590         catch ( final ModelException e )
1591         {
1592             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1593         }
1594         catch ( final JAXBException e )
1595         {
1596             String message = Messages.getMessage( e );
1597             if ( message == null && e.getLinkedException() != null )
1598             {
1599                 message = Messages.getMessage( e.getLinkedException() );
1600             }
1601 
1602             throw new BuildException( message, e, this.getLocation() );
1603         }
1604     }
1605 
1606     /**
1607      * Creates and returns a copy of this object.
1608      *
1609      * @return A copy of this object.
1610      */
1611     @Override
1612     public JomcTask clone()
1613     {
1614         try
1615         {
1616             final JomcTask clone = (JomcTask) super.clone();
1617             clone.classpath = (Path) ( this.classpath != null ? this.classpath.clone() : null );
1618 
1619             if ( this.modelContextAttributes != null )
1620             {
1621                 clone.modelContextAttributes = new ArrayList<KeyValueType>( this.modelContextAttributes.size() );
1622 
1623                 for ( KeyValueType e : this.modelContextAttributes )
1624                 {
1625                     clone.modelContextAttributes.add( e.clone() );
1626                 }
1627             }
1628 
1629             if ( this.transformationParameters != null )
1630             {
1631                 clone.transformationParameters =
1632                     new ArrayList<KeyValueType>( this.transformationParameters.size() );
1633 
1634                 for ( KeyValueType e : this.transformationParameters )
1635                 {
1636                     clone.transformationParameters.add( e.clone() );
1637                 }
1638             }
1639 
1640             if ( this.transformationParameterResources != null )
1641             {
1642                 clone.transformationParameterResources =
1643                     new ArrayList<PropertiesResourceType>( this.transformationParameterResources.size() );
1644 
1645                 for ( PropertiesResourceType e : this.transformationParameterResources )
1646                 {
1647                     clone.transformationParameterResources.add( e.clone() );
1648                 }
1649             }
1650 
1651             if ( this.transformationOutputProperties != null )
1652             {
1653                 clone.transformationOutputProperties =
1654                     new ArrayList<KeyValueType>( this.transformationOutputProperties.size() );
1655 
1656                 for ( KeyValueType e : this.transformationOutputProperties )
1657                 {
1658                     clone.transformationOutputProperties.add( e.clone() );
1659                 }
1660             }
1661 
1662             return clone;
1663         }
1664         catch ( final CloneNotSupportedException e )
1665         {
1666             throw new AssertionError( e );
1667         }
1668     }
1669 
1670 }