001/*
002 *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: JomcTask.java 5301 2016-08-30 02:04:33Z schulte $
029 *
030 */
031package org.jomc.ant;
032
033import java.io.BufferedReader;
034import java.io.File;
035import java.io.IOException;
036import java.io.InputStream;
037import java.io.StringReader;
038import java.io.StringWriter;
039import java.net.HttpURLConnection;
040import java.net.MalformedURLException;
041import java.net.SocketTimeoutException;
042import java.net.URI;
043import java.net.URISyntaxException;
044import java.net.URL;
045import java.net.URLConnection;
046import java.util.ArrayList;
047import java.util.Collection;
048import java.util.Enumeration;
049import java.util.HashSet;
050import java.util.Iterator;
051import java.util.LinkedList;
052import java.util.List;
053import java.util.Locale;
054import java.util.Map;
055import java.util.Properties;
056import java.util.Set;
057import java.util.concurrent.ExecutorService;
058import java.util.concurrent.Executors;
059import java.util.concurrent.ThreadFactory;
060import java.util.concurrent.atomic.AtomicInteger;
061import java.util.logging.Level;
062import javax.xml.bind.JAXBException;
063import javax.xml.bind.Marshaller;
064import javax.xml.transform.ErrorListener;
065import javax.xml.transform.Transformer;
066import javax.xml.transform.TransformerConfigurationException;
067import javax.xml.transform.TransformerException;
068import javax.xml.transform.TransformerFactory;
069import javax.xml.transform.stream.StreamSource;
070import org.apache.tools.ant.BuildException;
071import org.apache.tools.ant.Project;
072import org.apache.tools.ant.PropertyHelper;
073import org.apache.tools.ant.Task;
074import org.apache.tools.ant.types.Path;
075import org.apache.tools.ant.types.Reference;
076import org.jomc.ant.types.KeyValueType;
077import org.jomc.ant.types.NameType;
078import org.jomc.ant.types.PropertiesFormatType;
079import org.jomc.ant.types.PropertiesResourceType;
080import org.jomc.ant.types.ResourceType;
081import org.jomc.ant.types.TransformerResourceType;
082import org.jomc.model.ModelObject;
083import org.jomc.modlet.DefaultModelContext;
084import org.jomc.modlet.DefaultModletProvider;
085import org.jomc.modlet.Model;
086import org.jomc.modlet.ModelContext;
087import org.jomc.modlet.ModelContextFactory;
088import org.jomc.modlet.ModelException;
089import org.jomc.modlet.ModelValidationReport;
090import org.jomc.modlet.ModletProcessor;
091import org.jomc.modlet.ModletProvider;
092import org.jomc.modlet.ModletValidator;
093import org.jomc.modlet.ServiceFactory;
094
095/**
096 * Base class for executing tasks.
097 *
098 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
099 * @version $JOMC: JomcTask.java 5301 2016-08-30 02:04:33Z schulte $
100 * @see #execute()
101 */
102public 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}