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     */
031    package org.jomc.ant;
032    
033    import java.io.IOException;
034    import java.io.InputStream;
035    import java.net.SocketTimeoutException;
036    import java.net.URISyntaxException;
037    import java.net.URL;
038    import java.net.URLConnection;
039    import java.util.HashSet;
040    import java.util.Set;
041    import java.util.logging.Level;
042    import javax.xml.bind.JAXBElement;
043    import javax.xml.bind.JAXBException;
044    import javax.xml.bind.Unmarshaller;
045    import javax.xml.transform.Source;
046    import javax.xml.transform.stream.StreamSource;
047    import org.apache.tools.ant.BuildException;
048    import org.apache.tools.ant.Project;
049    import org.jomc.ant.types.KeyValueType;
050    import org.jomc.ant.types.ModuleResourceType;
051    import org.jomc.ant.types.ResourceType;
052    import org.jomc.model.Module;
053    import org.jomc.model.Modules;
054    import org.jomc.model.modlet.DefaultModelProcessor;
055    import org.jomc.model.modlet.DefaultModelProvider;
056    import org.jomc.model.modlet.ModelHelper;
057    import org.jomc.modlet.Model;
058    import org.jomc.modlet.ModelContext;
059    import org.jomc.modlet.ModelException;
060    import org.jomc.tools.modlet.ToolsModelProcessor;
061    import 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     */
069    public 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    }