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