001/*
002 *   Copyright (C) Christian Schulte, 2005-206
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: JomcModelTask.java 4200 2012-01-25 09:46:13Z schulte2005 $
029 *
030 */
031package org.jomc.ant;
032
033import java.io.IOException;
034import java.io.InputStream;
035import java.net.SocketTimeoutException;
036import java.net.URISyntaxException;
037import java.net.URL;
038import java.net.URLConnection;
039import java.util.HashSet;
040import java.util.Set;
041import java.util.logging.Level;
042import javax.xml.bind.JAXBElement;
043import javax.xml.bind.JAXBException;
044import javax.xml.bind.Unmarshaller;
045import javax.xml.transform.Source;
046import javax.xml.transform.stream.StreamSource;
047import org.apache.tools.ant.BuildException;
048import org.apache.tools.ant.Project;
049import org.jomc.ant.types.KeyValueType;
050import org.jomc.ant.types.ModuleResourceType;
051import org.jomc.ant.types.ResourceType;
052import org.jomc.model.Module;
053import org.jomc.model.Modules;
054import org.jomc.model.modlet.DefaultModelProcessor;
055import org.jomc.model.modlet.DefaultModelProvider;
056import org.jomc.model.modlet.ModelHelper;
057import org.jomc.modlet.Model;
058import org.jomc.modlet.ModelContext;
059import org.jomc.modlet.ModelException;
060import org.jomc.tools.modlet.ToolsModelProcessor;
061import org.jomc.tools.modlet.ToolsModelProvider;
062
063/**
064 * Base class for executing model based tasks.
065 *
066 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
067 * @version $JOMC: JomcModelTask.java 4200 2012-01-25 09:46:13Z schulte2005 $
068 */
069public class JomcModelTask extends JomcTask
070{
071
072    /** Controls model object class path resolution. */
073    private boolean modelObjectClasspathResolutionEnabled = true;
074
075    /** The location to search for modules. */
076    private String moduleLocation;
077
078    /** The location to search for transformers. */
079    private String transformerLocation;
080
081    /** Module resources. */
082    private Set<ModuleResourceType> moduleResources;
083
084    /** The flag indicating JAXP schema validation of model resources is enabled. */
085    private boolean modelResourceValidationEnabled = true;
086
087    /** Creates a new {@code JomcModelTask} instance. */
088    public JomcModelTask()
089    {
090        super();
091    }
092
093    /**
094     * Gets the location searched for modules.
095     *
096     * @return The location searched for modules or {@code null}.
097     *
098     * @see #setModuleLocation(java.lang.String)
099     */
100    public final String getModuleLocation()
101    {
102        return this.moduleLocation;
103    }
104
105    /**
106     * Sets the location to search for modules.
107     *
108     * @param value The new location to search for modules or {@code null}.
109     *
110     * @see #getModuleLocation()
111     */
112    public final void setModuleLocation( final String value )
113    {
114        this.moduleLocation = value;
115    }
116
117    /**
118     * Gets the location searched for transformers.
119     *
120     * @return The location searched for transformers or {@code null}.
121     *
122     * @see #setTransformerLocation(java.lang.String)
123     */
124    public final String getTransformerLocation()
125    {
126        return this.transformerLocation;
127    }
128
129    /**
130     * Sets the location to search for transformers.
131     *
132     * @param value The new location to search for transformers or {@code null}.
133     *
134     * @see #getTransformerLocation()
135     */
136    public final void setTransformerLocation( final String value )
137    {
138        this.transformerLocation = value;
139    }
140
141    /**
142     * Gets a flag indicating model object class path resolution is enabled.
143     *
144     * @return {@code true}, if model object class path resolution is enabled; {@code false}, else.
145     *
146     * @see #setModelObjectClasspathResolutionEnabled(boolean)
147     */
148    public final boolean isModelObjectClasspathResolutionEnabled()
149    {
150        return this.modelObjectClasspathResolutionEnabled;
151    }
152
153    /**
154     * Sets the flag indicating model object class path resolution is enabled.
155     *
156     * @param value {@code true}, to enable model object class path resolution; {@code false}, to disable model object
157     * class path resolution.
158     *
159     * @see #isModelObjectClasspathResolutionEnabled()
160     */
161    public final void setModelObjectClasspathResolutionEnabled( final boolean value )
162    {
163        this.modelObjectClasspathResolutionEnabled = value;
164    }
165
166    /**
167     * Gets a set of module resources.
168     * <p>This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
169     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
170     * module resources property.</p>
171     *
172     * @return A set of module resources.
173     *
174     * @see #createModuleResource()
175     */
176    public Set<ModuleResourceType> getModuleResources()
177    {
178        if ( this.moduleResources == null )
179        {
180            this.moduleResources = new HashSet<ModuleResourceType>();
181        }
182
183        return this.moduleResources;
184    }
185
186    /**
187     * Creates a new {@code moduleResource} element instance.
188     *
189     * @return A new {@code moduleResource} element instance.
190     *
191     * @see #getModuleResources()
192     */
193    public ModuleResourceType createModuleResource()
194    {
195        final ModuleResourceType moduleResource = new ModuleResourceType();
196        this.getModuleResources().add( moduleResource );
197        return moduleResource;
198    }
199
200    /**
201     * Gets a flag indicating JAXP schema validation of model resources is enabled.
202     *
203     * @return {@code true}, if JAXP schema validation of model resources is enabled; {@code false}, else.
204     *
205     * @see #setModelResourceValidationEnabled(boolean)
206     */
207    public final boolean isModelResourceValidationEnabled()
208    {
209        return this.modelResourceValidationEnabled;
210    }
211
212    /**
213     * Sets the flag indicating JAXP schema validation of model resources is enabled.
214     *
215     * @param value {@code true}, to enable JAXP schema validation of model resources; {@code false}, to disable JAXP
216     * schema validation of model resources.
217     *
218     * @see #isModelResourceValidationEnabled()
219     */
220    public final void setModelResourceValidationEnabled( final boolean value )
221    {
222        this.modelResourceValidationEnabled = value;
223    }
224
225    /**
226     * Gets a {@code Model} from a given {@code ModelContext}.
227     *
228     * @param context The context to get a {@code Model} from.
229     *
230     * @return The {@code Model} from {@code context}.
231     *
232     * @throws NullPointerException if {@code contexŧ} is {@code null}.
233     * @throws BuildException if no model is found.
234     * @throws ModelException if getting the model fails.
235     *
236     * @see #getModel()
237     * @see #isModelObjectClasspathResolutionEnabled()
238     * @see #isModelProcessingEnabled()
239     */
240    @Override
241    public Model getModel( final ModelContext context ) throws BuildException, ModelException
242    {
243        if ( context == null )
244        {
245            throw new NullPointerException( "context" );
246        }
247
248        Model model = new Model();
249        model.setIdentifier( this.getModel() );
250        Modules modules = new Modules();
251        ModelHelper.setModules( model, modules );
252        Unmarshaller unmarshaller = null;
253
254        for ( ResourceType resource : this.getModuleResources() )
255        {
256            final URL[] urls = this.getResources( context, resource.getLocation() );
257
258            if ( urls.length == 0 )
259            {
260                if ( resource.isOptional() )
261                {
262                    this.logMessage( Level.WARNING, Messages.getMessage( "moduleResourceNotFound",
263                                                                         resource.getLocation() ) );
264
265                }
266                else
267                {
268                    throw new BuildException( Messages.getMessage( "moduleResourceNotFound", resource.getLocation() ),
269                                              this.getLocation() );
270
271                }
272            }
273
274            for ( int i = urls.length - 1; i >= 0; i-- )
275            {
276                InputStream in = null;
277                boolean suppressExceptionOnClose = true;
278
279                try
280                {
281                    this.logMessage( Level.FINEST, Messages.getMessage( "reading", urls[i].toExternalForm() ) );
282
283                    final URLConnection con = urls[i].openConnection();
284                    con.setConnectTimeout( resource.getConnectTimeout() );
285                    con.setReadTimeout( resource.getReadTimeout() );
286                    con.connect();
287                    in = con.getInputStream();
288
289                    final Source source = new StreamSource( in, urls[i].toURI().toASCIIString() );
290
291                    if ( unmarshaller == null )
292                    {
293                        unmarshaller = context.createUnmarshaller( this.getModel() );
294                        if ( this.isModelResourceValidationEnabled() )
295                        {
296                            unmarshaller.setSchema( context.createSchema( this.getModel() ) );
297                        }
298                    }
299
300                    Object o = unmarshaller.unmarshal( source );
301                    if ( o instanceof JAXBElement<?> )
302                    {
303                        o = ( (JAXBElement<?>) o ).getValue();
304                    }
305
306                    if ( o instanceof Module )
307                    {
308                        modules.getModule().add( (Module) o );
309                    }
310                    else
311                    {
312                        this.log( Messages.getMessage( "unsupportedModuleResource", urls[i].toExternalForm() ),
313                                  Project.MSG_WARN );
314
315                    }
316
317                    suppressExceptionOnClose = false;
318                }
319                catch ( final SocketTimeoutException e )
320                {
321                    String message = Messages.getMessage( e );
322                    message = Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" );
323
324                    if ( resource.isOptional() )
325                    {
326                        this.getProject().log( message, e, Project.MSG_WARN );
327                    }
328                    else
329                    {
330                        throw new BuildException( message, e, this.getLocation() );
331                    }
332                }
333                catch ( final IOException e )
334                {
335                    String message = Messages.getMessage( e );
336                    message = Messages.getMessage( "resourceFailure", message != null ? " " + message : "" );
337
338                    if ( resource.isOptional() )
339                    {
340                        this.getProject().log( message, e, Project.MSG_WARN );
341                    }
342                    else
343                    {
344                        throw new BuildException( message, e, this.getLocation() );
345                    }
346                }
347                catch ( final URISyntaxException e )
348                {
349                    throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
350                }
351                catch ( final JAXBException e )
352                {
353                    String message = Messages.getMessage( e );
354                    if ( message == null )
355                    {
356                        message = Messages.getMessage( e.getLinkedException() );
357                    }
358
359                    throw new BuildException( message, e, this.getLocation() );
360                }
361                finally
362                {
363                    try
364                    {
365                        if ( in != null )
366                        {
367                            in.close();
368                        }
369                    }
370                    catch ( final IOException e )
371                    {
372                        if ( suppressExceptionOnClose )
373                        {
374                            this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
375                        }
376                        else
377                        {
378                            throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
379                        }
380                    }
381                }
382            }
383        }
384
385        model = context.findModel( model );
386        modules = ModelHelper.getModules( model );
387
388        if ( modules != null && this.isModelObjectClasspathResolutionEnabled() )
389        {
390            final Module classpathModule =
391                modules.getClasspathModule( Modules.getDefaultClasspathModuleName(), context.getClassLoader() );
392
393            if ( classpathModule != null && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
394            {
395                modules.getModule().add( classpathModule );
396            }
397        }
398
399        if ( this.isModelProcessingEnabled() )
400        {
401            model = context.processModel( model );
402        }
403
404        return model;
405    }
406
407    /** {@inheritDoc} */
408    @Override
409    public void preExecuteTask() throws BuildException
410    {
411        super.preExecuteTask();
412        this.assertLocationsNotNull( this.getModuleResources() );
413    }
414
415    /** {@inheritDoc} */
416    @Override
417    public ModelContext newModelContext( final ClassLoader classLoader ) throws ModelException
418    {
419        final ModelContext modelContext = super.newModelContext( classLoader );
420
421        if ( this.getTransformerLocation() != null )
422        {
423            modelContext.setAttribute( DefaultModelProcessor.TRANSFORMER_LOCATION_ATTRIBUTE_NAME,
424                                       this.getTransformerLocation() );
425
426        }
427
428        if ( this.getModuleLocation() != null )
429        {
430            modelContext.setAttribute( DefaultModelProvider.MODULE_LOCATION_ATTRIBUTE_NAME, this.getModuleLocation() );
431        }
432
433        modelContext.setAttribute( ToolsModelProvider.MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME,
434                                   this.isModelObjectClasspathResolutionEnabled() );
435
436        modelContext.setAttribute( ToolsModelProcessor.MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME,
437                                   this.isModelObjectClasspathResolutionEnabled() );
438
439        modelContext.setAttribute( DefaultModelProvider.VALIDATING_ATTRIBUTE_NAME,
440                                   this.isModelResourceValidationEnabled() );
441
442        for ( int i = 0, s0 = this.getModelContextAttributes().size(); i < s0; i++ )
443        {
444            final KeyValueType kv = this.getModelContextAttributes().get( i );
445            final Object object = kv.getObject( this.getLocation() );
446
447            if ( object != null )
448            {
449                modelContext.setAttribute( kv.getKey(), object );
450            }
451            else
452            {
453                modelContext.clearAttribute( kv.getKey() );
454            }
455        }
456
457
458        return modelContext;
459    }
460
461    /** {@inheritDoc} */
462    @Override
463    public JomcModelTask clone()
464    {
465        final JomcModelTask clone = (JomcModelTask) super.clone();
466
467        if ( this.moduleResources != null )
468        {
469            clone.moduleResources = new HashSet<ModuleResourceType>( this.moduleResources.size() );
470            for ( ModuleResourceType e : this.moduleResources )
471            {
472                clone.moduleResources.add( e.clone() );
473            }
474        }
475
476        return clone;
477    }
478
479}