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: JomcModelTask.java 5263 2016-05-01 23:44:12Z schulte $
029 *
030 */
031package org.jomc.ant;
032
033import java.io.IOException;
034import java.io.InputStream;
035import java.net.HttpURLConnection;
036import java.net.SocketTimeoutException;
037import java.net.URISyntaxException;
038import java.net.URL;
039import java.net.URLConnection;
040import java.util.HashSet;
041import java.util.Set;
042import java.util.logging.Level;
043import javax.xml.bind.JAXBElement;
044import javax.xml.bind.JAXBException;
045import javax.xml.bind.Unmarshaller;
046import javax.xml.transform.Source;
047import javax.xml.transform.stream.StreamSource;
048import org.apache.tools.ant.BuildException;
049import org.apache.tools.ant.Project;
050import org.jomc.ant.types.KeyValueType;
051import org.jomc.ant.types.ModuleResourceType;
052import org.jomc.ant.types.ResourceType;
053import org.jomc.model.Module;
054import org.jomc.model.Modules;
055import org.jomc.model.modlet.DefaultModelProcessor;
056import org.jomc.model.modlet.DefaultModelProvider;
057import org.jomc.model.modlet.DefaultModelValidator;
058import org.jomc.model.modlet.ModelHelper;
059import org.jomc.modlet.Model;
060import org.jomc.modlet.ModelContext;
061import org.jomc.modlet.ModelException;
062import org.jomc.tools.modlet.ToolsModelProcessor;
063import org.jomc.tools.modlet.ToolsModelProvider;
064
065/**
066 * Base class for executing model based tasks.
067 *
068 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
069 * @version $JOMC: JomcModelTask.java 5263 2016-05-01 23:44:12Z schulte $
070 */
071public class JomcModelTask extends JomcTask
072{
073
074    /**
075     * Controls model object class path resolution.
076     */
077    private boolean modelObjectClasspathResolutionEnabled = true;
078
079    /**
080     * The location to search for modules.
081     */
082    private String moduleLocation;
083
084    /**
085     * The location to search for transformers.
086     */
087    private String transformerLocation;
088
089    /**
090     * Module resources.
091     */
092    private Set<ModuleResourceType> moduleResources;
093
094    /**
095     * The flag indicating JAXP schema validation of model resources is enabled.
096     */
097    private boolean modelResourceValidationEnabled = true;
098
099    /**
100     * The flag indicating Java validation is enabled.
101     */
102    private boolean javaValidationEnabled = true;
103
104    /**
105     * Creates a new {@code JomcModelTask} instance.
106     */
107    public JomcModelTask()
108    {
109        super();
110    }
111
112    /**
113     * Gets the location searched for modules.
114     *
115     * @return The location searched for modules or {@code null}.
116     *
117     * @see #setModuleLocation(java.lang.String)
118     */
119    public final String getModuleLocation()
120    {
121        return this.moduleLocation;
122    }
123
124    /**
125     * Sets the location to search for modules.
126     *
127     * @param value The new location to search for modules or {@code null}.
128     *
129     * @see #getModuleLocation()
130     */
131    public final void setModuleLocation( final String value )
132    {
133        this.moduleLocation = value;
134    }
135
136    /**
137     * Gets the location searched for transformers.
138     *
139     * @return The location searched for transformers or {@code null}.
140     *
141     * @see #setTransformerLocation(java.lang.String)
142     */
143    public final String getTransformerLocation()
144    {
145        return this.transformerLocation;
146    }
147
148    /**
149     * Sets the location to search for transformers.
150     *
151     * @param value The new location to search for transformers or {@code null}.
152     *
153     * @see #getTransformerLocation()
154     */
155    public final void setTransformerLocation( final String value )
156    {
157        this.transformerLocation = value;
158    }
159
160    /**
161     * Gets a flag indicating model object class path resolution is enabled.
162     *
163     * @return {@code true}, if model object class path resolution is enabled; {@code false}, else.
164     *
165     * @see #setModelObjectClasspathResolutionEnabled(boolean)
166     */
167    public final boolean isModelObjectClasspathResolutionEnabled()
168    {
169        return this.modelObjectClasspathResolutionEnabled;
170    }
171
172    /**
173     * Sets the flag indicating model object class path resolution is enabled.
174     *
175     * @param value {@code true}, to enable model object class path resolution; {@code false}, to disable model object
176     * class path resolution.
177     *
178     * @see #isModelObjectClasspathResolutionEnabled()
179     */
180    public final void setModelObjectClasspathResolutionEnabled( final boolean value )
181    {
182        this.modelObjectClasspathResolutionEnabled = value;
183    }
184
185    /**
186     * Gets a set of module resources.
187     * <p>
188     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
189     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
190     * module resources property.
191     * </p>
192     *
193     * @return A set of module resources.
194     *
195     * @see #createModuleResource()
196     */
197    public Set<ModuleResourceType> getModuleResources()
198    {
199        if ( this.moduleResources == null )
200        {
201            this.moduleResources = new HashSet<ModuleResourceType>();
202        }
203
204        return this.moduleResources;
205    }
206
207    /**
208     * Creates a new {@code moduleResource} element instance.
209     *
210     * @return A new {@code moduleResource} element instance.
211     *
212     * @see #getModuleResources()
213     */
214    public ModuleResourceType createModuleResource()
215    {
216        final ModuleResourceType moduleResource = new ModuleResourceType();
217        this.getModuleResources().add( moduleResource );
218        return moduleResource;
219    }
220
221    /**
222     * Gets a flag indicating JAXP schema validation of model resources is enabled.
223     *
224     * @return {@code true}, if JAXP schema validation of model resources is enabled; {@code false}, else.
225     *
226     * @see #setModelResourceValidationEnabled(boolean)
227     */
228    public final boolean isModelResourceValidationEnabled()
229    {
230        return this.modelResourceValidationEnabled;
231    }
232
233    /**
234     * Sets the flag indicating JAXP schema validation of model resources is enabled.
235     *
236     * @param value {@code true}, to enable JAXP schema validation of model resources; {@code false}, to disable JAXP
237     * schema validation of model resources.
238     *
239     * @see #isModelResourceValidationEnabled()
240     */
241    public final void setModelResourceValidationEnabled( final boolean value )
242    {
243        this.modelResourceValidationEnabled = value;
244    }
245
246    /**
247     * Gets a flag indicating Java validation is enabled.
248     *
249     * @return {@code true}, if Java validation is enabled; {@code false}, else.
250     *
251     * @see #setJavaValidationEnabled(boolean)
252     *
253     * @since 1.4
254     */
255    public final boolean isJavaValidationEnabled()
256    {
257        return this.javaValidationEnabled;
258    }
259
260    /**
261     * Sets the flag indicating Java validation is enabled.
262     *
263     * @param value {@code true}, to enable Java validation; {@code false}, to disable Java validation.
264     *
265     * @see #isJavaValidationEnabled()
266     *
267     * @since 1.4
268     */
269    public final void setJavaValidationEnabled( final boolean value )
270    {
271        this.javaValidationEnabled = value;
272    }
273
274    /**
275     * Gets a {@code Model} from a given {@code ModelContext}.
276     *
277     * @param context The context to get a {@code Model} from.
278     *
279     * @return The {@code Model} from {@code context}.
280     *
281     * @throws NullPointerException if {@code contexŧ} is {@code null}.
282     * @throws BuildException if no model is found.
283     * @throws ModelException if getting the model fails.
284     *
285     * @see #getModel()
286     * @see #isModelObjectClasspathResolutionEnabled()
287     * @see #isModelProcessingEnabled()
288     */
289    @Override
290    public Model getModel( final ModelContext context ) throws BuildException, ModelException
291    {
292        if ( context == null )
293        {
294            throw new NullPointerException( "context" );
295        }
296
297        Model model = new Model();
298        model.setIdentifier( this.getModel() );
299        Modules modules = new Modules();
300        ModelHelper.setModules( model, modules );
301        Unmarshaller unmarshaller = null;
302
303        for ( final ResourceType resource : this.getModuleResources() )
304        {
305            final URL[] urls = this.getResources( context, resource.getLocation() );
306
307            if ( urls.length == 0 )
308            {
309                if ( resource.isOptional() )
310                {
311                    this.logMessage( Level.WARNING, Messages.getMessage( "moduleResourceNotFound",
312                                                                         resource.getLocation() ) );
313
314                }
315                else
316                {
317                    throw new BuildException( Messages.getMessage( "moduleResourceNotFound", resource.getLocation() ),
318                                              this.getLocation() );
319
320                }
321            }
322
323            for ( int i = urls.length - 1; i >= 0; i-- )
324            {
325                URLConnection con = null;
326                InputStream in = null;
327
328                try
329                {
330                    this.logMessage( Level.FINEST, Messages.getMessage( "reading", urls[i].toExternalForm() ) );
331
332                    con = urls[i].openConnection();
333                    con.setConnectTimeout( resource.getConnectTimeout() );
334                    con.setReadTimeout( resource.getReadTimeout() );
335                    con.connect();
336                    in = con.getInputStream();
337
338                    final Source source = new StreamSource( in, urls[i].toURI().toASCIIString() );
339
340                    if ( unmarshaller == null )
341                    {
342                        unmarshaller = context.createUnmarshaller( this.getModel() );
343                        if ( this.isModelResourceValidationEnabled() )
344                        {
345                            unmarshaller.setSchema( context.createSchema( this.getModel() ) );
346                        }
347                    }
348
349                    Object o = unmarshaller.unmarshal( source );
350                    if ( o instanceof JAXBElement<?> )
351                    {
352                        o = ( (JAXBElement<?>) o ).getValue();
353                    }
354
355                    if ( o instanceof Module )
356                    {
357                        modules.getModule().add( (Module) o );
358                    }
359                    else
360                    {
361                        this.log( Messages.getMessage( "unsupportedModuleResource", urls[i].toExternalForm() ),
362                                  Project.MSG_WARN );
363
364                    }
365
366                    in.close();
367                    in = null;
368                }
369                catch ( final SocketTimeoutException e )
370                {
371                    String message = Messages.getMessage( e );
372                    message = Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" );
373
374                    if ( resource.isOptional() )
375                    {
376                        this.getProject().log( message, e, Project.MSG_WARN );
377                    }
378                    else
379                    {
380                        throw new BuildException( message, e, this.getLocation() );
381                    }
382                }
383                catch ( final IOException e )
384                {
385                    String message = Messages.getMessage( e );
386                    message = Messages.getMessage( "resourceFailure", message != null ? " " + message : "" );
387
388                    if ( resource.isOptional() )
389                    {
390                        this.getProject().log( message, e, Project.MSG_WARN );
391                    }
392                    else
393                    {
394                        throw new BuildException( message, e, this.getLocation() );
395                    }
396                }
397                catch ( final URISyntaxException e )
398                {
399                    throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
400                }
401                catch ( final JAXBException e )
402                {
403                    String message = Messages.getMessage( e );
404                    if ( message == null )
405                    {
406                        message = Messages.getMessage( e.getLinkedException() );
407                    }
408
409                    throw new BuildException( message, e, this.getLocation() );
410                }
411                finally
412                {
413                    try
414                    {
415                        if ( in != null )
416                        {
417                            in.close();
418                        }
419                    }
420                    catch ( final IOException e )
421                    {
422                        this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
423                    }
424                    finally
425                    {
426                        if ( con instanceof HttpURLConnection )
427                        {
428                            ( (HttpURLConnection) con ).disconnect();
429                        }
430                    }
431                }
432            }
433        }
434
435        model = context.findModel( model );
436        modules = ModelHelper.getModules( model );
437
438        if ( modules != null && this.isModelObjectClasspathResolutionEnabled() )
439        {
440            final Module classpathModule =
441                modules.getClasspathModule( Modules.getDefaultClasspathModuleName(), context.getClassLoader() );
442
443            if ( classpathModule != null && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
444            {
445                modules.getModule().add( classpathModule );
446            }
447        }
448
449        if ( this.isModelProcessingEnabled() )
450        {
451            model = context.processModel( model );
452        }
453
454        return model;
455    }
456
457    /**
458     * {@inheritDoc}
459     */
460    @Override
461    public void preExecuteTask() throws BuildException
462    {
463        super.preExecuteTask();
464        this.assertLocationsNotNull( this.getModuleResources() );
465    }
466
467    /**
468     * {@inheritDoc}
469     */
470    @Override
471    public ModelContext newModelContext( final ClassLoader classLoader ) throws ModelException
472    {
473        final ModelContext modelContext = super.newModelContext( classLoader );
474
475        if ( this.getTransformerLocation() != null )
476        {
477            modelContext.setAttribute( DefaultModelProcessor.TRANSFORMER_LOCATION_ATTRIBUTE_NAME,
478                                       this.getTransformerLocation() );
479
480        }
481
482        if ( this.getModuleLocation() != null )
483        {
484            modelContext.setAttribute( DefaultModelProvider.MODULE_LOCATION_ATTRIBUTE_NAME, this.getModuleLocation() );
485        }
486
487        modelContext.setAttribute( ToolsModelProvider.MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME,
488                                   this.isModelObjectClasspathResolutionEnabled() );
489
490        modelContext.setAttribute( ToolsModelProcessor.MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME,
491                                   this.isModelObjectClasspathResolutionEnabled() );
492
493        modelContext.setAttribute( DefaultModelProvider.VALIDATING_ATTRIBUTE_NAME,
494                                   this.isModelResourceValidationEnabled() );
495
496        modelContext.setAttribute( DefaultModelValidator.VALIDATE_JAVA_ATTRIBUTE_NAME, this.isJavaValidationEnabled() );
497
498        for ( int i = 0, s0 = this.getModelContextAttributes().size(); i < s0; i++ )
499        {
500            final KeyValueType kv = this.getModelContextAttributes().get( i );
501            final Object object = kv.getObject( this.getLocation() );
502
503            if ( object != null )
504            {
505                modelContext.setAttribute( kv.getKey(), object );
506            }
507            else
508            {
509                modelContext.clearAttribute( kv.getKey() );
510            }
511        }
512
513        return modelContext;
514    }
515
516    /**
517     * {@inheritDoc}
518     */
519    @Override
520    public JomcModelTask clone()
521    {
522        final JomcModelTask clone = (JomcModelTask) super.clone();
523
524        if ( this.moduleResources != null )
525        {
526            clone.moduleResources = new HashSet<ModuleResourceType>( this.moduleResources.size() );
527            for ( final ModuleResourceType e : this.moduleResources )
528            {
529                clone.moduleResources.add( e.clone() );
530            }
531        }
532
533        return clone;
534    }
535
536}