001/*
002 *   Copyright (C) Christian Schulte <cs@schulte.it>, 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: JomcContainerDescriptorHandler.java 5043 2015-05-27 07:03:39Z schulte $
029 *
030 */
031package org.jomc.mojo;
032
033import java.io.File;
034import java.io.IOException;
035import java.io.InputStream;
036import java.net.MalformedURLException;
037import java.net.URISyntaxException;
038import java.net.URL;
039import java.util.Collections;
040import java.util.Iterator;
041import java.util.LinkedList;
042import java.util.List;
043import java.util.Map;
044import javax.xml.bind.JAXBElement;
045import javax.xml.bind.JAXBException;
046import javax.xml.bind.Marshaller;
047import javax.xml.bind.Unmarshaller;
048import javax.xml.bind.util.JAXBResult;
049import javax.xml.bind.util.JAXBSource;
050import javax.xml.transform.Transformer;
051import javax.xml.transform.TransformerConfigurationException;
052import javax.xml.transform.TransformerException;
053import javax.xml.transform.TransformerFactory;
054import javax.xml.transform.stream.StreamSource;
055import org.apache.maven.plugin.assembly.filter.ContainerDescriptorHandler;
056import org.codehaus.plexus.archiver.Archiver;
057import org.codehaus.plexus.archiver.ArchiverException;
058import org.codehaus.plexus.archiver.ResourceIterator;
059import org.codehaus.plexus.archiver.UnArchiver;
060import org.codehaus.plexus.components.io.fileselectors.FileInfo;
061import org.codehaus.plexus.logging.AbstractLogEnabled;
062import org.codehaus.plexus.util.StringUtils;
063import org.jomc.model.ModelObject;
064import org.jomc.model.Module;
065import org.jomc.model.Modules;
066import org.jomc.model.modlet.DefaultModelProvider;
067import org.jomc.modlet.DefaultModelContext;
068import org.jomc.modlet.DefaultModletProvider;
069import org.jomc.modlet.ModelContext;
070import org.jomc.modlet.ModelContextFactory;
071import org.jomc.modlet.ModelException;
072import org.jomc.modlet.Modlet;
073import org.jomc.modlet.ModletObject;
074import org.jomc.modlet.Modlets;
075
076/**
077 * Maven Assembly Plugin {@code ContainerDescriptorHandler} implementation for assembling JOMC resources.
078 *
079 * <p>
080 * <b>Maven Assembly Plugin Usage</b><pre>
081 * &lt;containerDescriptorHandler&gt;
082 *   &lt;handlerName&gt;JOMC&lt;/handlerName&gt;
083 *   &lt;configuration&gt;
084 *     &lt;model&gt;http://jomc.org/model&lt;/model&gt;
085 *     &lt;modelContextFactoryClassName&gt;class name&lt;/modelContextFactoryClassName&gt;
086 *     &lt;modelContextAttributes&gt;
087 *       &lt;modelContextAttribute&gt;
088 *         &lt;key&gt;The name of the attribute&lt;/key&gt;
089 *         &lt;value&gt;The name of the attribute&lt;/value&gt;
090 *         &lt;type&gt;The name of the class of the object.&lt;/type&gt;
091 *       &lt;/modelContextAttribute&gt;
092 *     &lt;/modelContextAttributes/&gt;
093 *     &lt;moduleEncoding&gt;${project.build.sourceEncoding}&lt;/moduleEncoding&gt;
094 *     &lt;moduleName&gt;${project.name}&lt;/moduleName&gt;
095 *     &lt;moduleVersion&gt;${project.version}&lt;/moduleVersion&gt;
096 *     &lt;moduleVendor&gt;${project.organization.name}&lt;/moduleVendor&gt;
097 *     &lt;moduleResource&gt;META-INF/custom-jomc.xml&lt;/moduleResource&gt;
098 *     &lt;moduleResources&gt;
099 *       &lt;moduleResource&gt;META-INF/jomc.xml&lt;/moduleResource&gt;
100 *     &lt;/moduleResources&gt;
101 *     &lt;moduleIncludes&gt;
102 *       &lt;moduleInclude&gt;module name&lt;/moduleInclude&gt;
103 *     &lt;/moduleIncludes&gt;
104 *     &lt;moduleExcludes&gt;
105 *       &lt;moduleExclude&gt;module name&lt;/moduleExclude&gt;
106 *     &lt;/moduleExcludes&gt;
107 *     &lt;modletEncoding&gt;${project.build.sourceEncoding}&lt;/modletEncoding&gt;
108 *     &lt;modletName&gt;${project.name}&lt;/modletName&gt;
109 *     &lt;modletVersion&gt;${project.version}&lt;/modletVersion&gt;
110 *     &lt;modletVendor&gt;${project.organization.name}&lt;/modletVendor&gt;
111 *     &lt;modletResource&gt;META-INF/custom-jomc-modlet.xml&lt;/modletResource&gt;
112 *     &lt;modletResources&gt;
113 *       &lt;modletResource&gt;META-INF/jomc-modlet.xml&lt;/modletResource&gt;
114 *     &lt;/modletResources&gt;
115 *     &lt;modletIncludes&gt;
116 *       &lt;modletInclude&gt;modlet name&lt;/modletInclude&gt;
117 *     &lt;/modletIncludes&gt;
118 *     &lt;modletExcludes&gt;
119 *       &lt;modletExclude&gt;modlet name&lt;/modletExclude&gt;
120 *     &lt;/modletExcludes&gt;
121 *     &lt;modelObjectStylesheet&gt;Location of a XSLT document to use for transforming the merged model document.&lt;/modelObjectStylesheet&gt;
122 *     &lt;modletObjectStylesheet&gt;Location of a XSLT document to use for transforming the merged modlet document.&lt;/modletObjectStylesheet&gt;
123 *     &lt;providerLocation&gt;META-INF/custom-services&lt;/providerLocation&gt;
124 *     &lt;platformProviderLocation&gt;${java.home}/jre/lib/custom-jomc.properties&lt;/platformProviderLocation&gt;
125 *     &lt;modletLocation&gt;META-INF/custom-jomc-modlet.xml&lt;/modletLocation&gt;
126 *     &lt;modletSchemaSystemId&gt;http://custom.host.tld/custom/path/jomc-modlet-1.9.xsd&lt;/modletSchemaSystemId&gt;
127 *   &lt;/configuration&gt;
128 * &lt;/containerDescriptorHandler&gt;
129 * </pre></p>
130 *
131 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
132 * @version $JOMC: JomcContainerDescriptorHandler.java 5043 2015-05-27 07:03:39Z schulte $
133 * @since 1.2
134 * @plexus.component role="org.apache.maven.plugin.assembly.filter.ContainerDescriptorHandler"
135 * role-hint="JOMC"
136 */
137public class JomcContainerDescriptorHandler extends AbstractLogEnabled implements ContainerDescriptorHandler
138{
139
140    /**
141     * Prefix prepended to log messages.
142     */
143    private static final String LOG_PREFIX = "[JOMC] ";
144
145    /**
146     * The identifier of the model to process.
147     */
148    private String model = ModelObject.MODEL_PUBLIC_ID;
149
150    /**
151     * The encoding of the assembled module.
152     */
153    private String moduleEncoding;
154
155    /**
156     * The name of the assembled module.
157     */
158    private String moduleName;
159
160    /**
161     * The version of the assembled module.
162     */
163    private String moduleVersion;
164
165    /**
166     * The vendor of the assembled module.
167     */
168    private String moduleVendor;
169
170    /**
171     * The resource name of the assembled module.
172     */
173    private String moduleResource = DefaultModelProvider.getDefaultModuleLocation();
174
175    /**
176     * Names of resources to process.
177     */
178    private String[] moduleResources =
179    {
180        DefaultModelProvider.getDefaultModuleLocation()
181    };
182
183    /**
184     * Included modules.
185     */
186    private List<String> moduleIncludes;
187
188    /**
189     * Excluded modules.
190     */
191    private List<String> moduleExcludes;
192
193    /**
194     * The encoding of the assembled modlet.
195     */
196    private String modletEncoding;
197
198    /**
199     * The name of the assembled modlet.
200     */
201    private String modletName;
202
203    /**
204     * The version of the assembled modlet.
205     */
206    private String modletVersion;
207
208    /**
209     * The vendor of the assembled modlet.
210     */
211    private String modletVendor;
212
213    /**
214     * The resource name of the assembled modlet resources.
215     */
216    private String modletResource = DefaultModletProvider.getDefaultModletLocation();
217
218    /**
219     * Names of modlet resources to process.
220     */
221    private String[] modletResources =
222    {
223        DefaultModletProvider.getDefaultModletLocation()
224    };
225
226    /**
227     * Included modlets.
228     */
229    private List<String> modletIncludes;
230
231    /**
232     * Excluded modlets.
233     */
234    private List<String> modletExcludes;
235
236    /**
237     * Location of a XSLT document to use for transforming the merged model document.
238     */
239    private String modelObjectStylesheet;
240
241    /**
242     * Location of a XSLT document to use for transforming the merged modlet document.
243     */
244    private String modletObjectStylesheet;
245
246    /**
247     * The location to search for providers.
248     */
249    private String providerLocation;
250
251    /**
252     * The location to search for platform providers.
253     */
254    private String platformProviderLocation;
255
256    /**
257     * The system id of the modlet schema.
258     */
259    private String modletSchemaSystemId;
260
261    /**
262     * The location to search for modlets.
263     */
264    private String modletLocation;
265
266    /**
267     * Name of the {@code ModelContextFactory} implementation class.
268     */
269    private String modelContextFactoryClassName;
270
271    /**
272     * {@code ModelContext} attributes to apply.
273     */
274    private List<ModelContextAttribute> modelContextAttributes;
275
276    /**
277     * Modlet resources.
278     */
279    private Modlets modlets = new Modlets();
280
281    /**
282     * Model resources.
283     */
284    private Modules modules = new Modules();
285
286    /**
287     * The JOMC JAXB marshaller of the instance.
288     */
289    private Marshaller jomcMarshaller;
290
291    /**
292     * The JOMC JAXB unmarshaller of the instance.
293     */
294    private Unmarshaller jomcUnmarshaller;
295
296    /**
297     * The modlet JAXB marshaller of the instance.
298     */
299    private Marshaller modletMarshaller;
300
301    /**
302     * The modlet JAXB unmarshaller of the instance.
303     */
304    private Unmarshaller modletUnmarshaller;
305
306    /**
307     * Creates a new {@code JomcContainerDescriptorHandler} instance.
308     */
309    public JomcContainerDescriptorHandler()
310    {
311        super();
312    }
313
314    public void finalizeArchiveCreation( final Archiver archiver ) throws ArchiverException
315    {
316        if ( StringUtils.isEmpty( this.model ) )
317        {
318            throw new ArchiverException( Messages.getMessage( "mandatoryParameter", "model" ) );
319        }
320        if ( StringUtils.isEmpty( this.modletName ) )
321        {
322            throw new ArchiverException( Messages.getMessage( "mandatoryParameter", "modletName" ) );
323        }
324        if ( StringUtils.isEmpty( this.modletResource ) )
325        {
326            throw new ArchiverException( Messages.getMessage( "mandatoryParameter", "modletResource" ) );
327        }
328        if ( StringUtils.isEmpty( this.moduleName ) )
329        {
330            throw new ArchiverException( Messages.getMessage( "mandatoryParameter", "moduleName" ) );
331        }
332        if ( StringUtils.isEmpty( this.moduleResource ) )
333        {
334            throw new ArchiverException( Messages.getMessage( "mandatoryParameter", "moduleResource" ) );
335        }
336
337        try
338        {
339            // This will prompt the isSelected() call, below, for all resources added to the archive. This needs to be
340            // corrected in the AbstractArchiver, where runArchiveFinalizers() is called before regular resources are
341            // added, which is done because the manifest needs to be added first, and the manifest-creation component is
342            // a finalizer in the assembly plugin.
343            for ( final ResourceIterator it = archiver.getResources(); it.hasNext(); it.next() );
344
345            if ( !this.modules.getModule().isEmpty() )
346            {
347                if ( this.moduleIncludes != null )
348                {
349                    for ( final Iterator<Module> it = this.modules.getModule().iterator(); it.hasNext(); )
350                    {
351                        final Module m = it.next();
352
353                        if ( !this.moduleIncludes.contains( m.getName() ) )
354                        {
355                            it.remove();
356
357                            if ( this.getLogger() != null && this.getLogger().isInfoEnabled() )
358                            {
359                                this.getLogger().info( LOG_PREFIX + Messages.getMessage(
360                                    "excludingModule", m.getName() ) );
361
362                            }
363                        }
364                    }
365                }
366
367                if ( this.moduleExcludes != null )
368                {
369                    for ( final String exclude : this.moduleExcludes )
370                    {
371                        final Module excluded = this.modules.getModule( exclude );
372
373                        if ( excluded != null )
374                        {
375                            this.modules.getModule().remove( excluded );
376
377                            if ( this.getLogger() != null && this.getLogger().isInfoEnabled() )
378                            {
379                                this.getLogger().info( LOG_PREFIX + Messages.getMessage(
380                                    "excludingModule", excluded.getName() ) );
381
382                            }
383                        }
384                    }
385                }
386
387                if ( this.getLogger() != null && this.getLogger().isInfoEnabled() )
388                {
389                    for ( final Module m : this.modules.getModule() )
390                    {
391                        this.getLogger().info( LOG_PREFIX + Messages.getMessage( "includingModule", m.getName() ) );
392                    }
393                }
394
395                final Module mergedModule = this.modules.getMergedModule( this.moduleName );
396                mergedModule.setVersion( this.moduleVersion );
397                mergedModule.setVendor( this.moduleVendor );
398
399                final JAXBElement<Module> transformedModule = this.transformModelObject(
400                    new org.jomc.model.ObjectFactory().createModule( mergedModule ), Module.class );
401
402                final File moduleFile = File.createTempFile( "maven-assembly-plugin", ".tmp" );
403                moduleFile.deleteOnExit();
404
405                this.marshalModelObject( transformedModule, moduleFile );
406
407                archiver.addFile( moduleFile, normalizeResourceName( this.moduleResource ) );
408            }
409
410            if ( !this.modlets.getModlet().isEmpty() )
411            {
412                if ( this.modletIncludes != null )
413                {
414                    for ( final Iterator<Modlet> it = this.modlets.getModlet().iterator(); it.hasNext(); )
415                    {
416                        final Modlet m = it.next();
417
418                        if ( !this.modletIncludes.contains( m.getName() ) )
419                        {
420                            it.remove();
421
422                            if ( this.getLogger() != null && this.getLogger().isInfoEnabled() )
423                            {
424                                this.getLogger().info( LOG_PREFIX + Messages.getMessage(
425                                    "excludingModlet", m.getName() ) );
426
427                            }
428                        }
429                    }
430                }
431
432                if ( this.modletExcludes != null )
433                {
434                    for ( final String exclude : this.modletExcludes )
435                    {
436                        final Modlet excluded = this.modlets.getModlet( exclude );
437
438                        if ( excluded != null )
439                        {
440                            this.modlets.getModlet().remove( excluded );
441
442                            if ( this.getLogger() != null && this.getLogger().isInfoEnabled() )
443                            {
444                                this.getLogger().info( LOG_PREFIX + Messages.getMessage(
445                                    "excludingModlet", excluded.getName() ) );
446
447                            }
448                        }
449                    }
450                }
451
452                if ( this.getLogger() != null && this.getLogger().isInfoEnabled() )
453                {
454                    for ( final Modlet m : this.modlets.getModlet() )
455                    {
456                        this.getLogger().info( LOG_PREFIX + Messages.getMessage( "includingModlet", m.getName() ) );
457                    }
458                }
459
460                final Modlet mergedModlet = this.modlets.getMergedModlet( this.modletName, this.model );
461                mergedModlet.setVendor( this.modletVendor );
462                mergedModlet.setVersion( this.modletVersion );
463
464                final JAXBElement<Modlet> transformedModlet = this.transformModletObject(
465                    new org.jomc.modlet.ObjectFactory().createModlet( mergedModlet ), Modlet.class );
466
467                final File modletFile = File.createTempFile( "maven-assembly-plugin", ".tmp" );
468                modletFile.deleteOnExit();
469
470                this.marshalModletObject( transformedModlet, modletFile );
471
472                archiver.addFile( modletFile, normalizeResourceName( this.modletResource ) );
473            }
474        }
475        catch ( final InstantiationException e )
476        {
477            throw new ArchiverException( Messages.getMessage( e ), e );
478        }
479        catch ( final TransformerConfigurationException e )
480        {
481            String message = Messages.getMessage( e );
482            if ( message == null && e.getException() != null )
483            {
484                message = Messages.getMessage( e.getException() );
485            }
486
487            throw new ArchiverException( message, e );
488        }
489        catch ( final TransformerException e )
490        {
491            String message = Messages.getMessage( e );
492            if ( message == null && e.getException() != null )
493            {
494                message = Messages.getMessage( e.getException() );
495            }
496
497            throw new ArchiverException( message, e );
498        }
499        catch ( final JAXBException e )
500        {
501            String message = Messages.getMessage( e );
502            if ( message == null && e.getLinkedException() != null )
503            {
504                message = Messages.getMessage( e.getLinkedException() );
505            }
506
507            throw new ArchiverException( message, e );
508        }
509        catch ( final ModelException e )
510        {
511            throw new ArchiverException( Messages.getMessage( e ), e );
512        }
513        catch ( final IOException e )
514        {
515            throw new ArchiverException( Messages.getMessage( e ), e );
516        }
517        catch ( final URISyntaxException e )
518        {
519            throw new ArchiverException( Messages.getMessage( e ), e );
520        }
521        finally
522        {
523            this.modlets = new Modlets();
524            this.modules = new Modules();
525            this.jomcMarshaller = null;
526            this.jomcUnmarshaller = null;
527            this.modletMarshaller = null;
528            this.modletUnmarshaller = null;
529        }
530    }
531
532    public void finalizeArchiveExtraction( final UnArchiver unarchiver ) throws ArchiverException
533    {
534    }
535
536    public List<String> getVirtualFiles()
537    {
538        final List<String> virtualFiles = new LinkedList<String>();
539
540        if ( !this.modlets.getModlet().isEmpty() )
541        {
542            virtualFiles.add( normalizeResourceName( this.modletResource ) );
543        }
544
545        if ( !this.modules.getModule().isEmpty() )
546        {
547            virtualFiles.add( normalizeResourceName( this.moduleResource ) );
548        }
549
550        return virtualFiles.isEmpty() ? null : Collections.unmodifiableList( virtualFiles );
551    }
552
553    public boolean isSelected( final FileInfo fileInfo ) throws IOException
554    {
555        try
556        {
557            boolean selected = true;
558            final String name = normalizeResourceName( fileInfo.getName() );
559
560            if ( this.moduleResources != null )
561            {
562                for ( final String r : this.moduleResources )
563                {
564                    if ( name.equals( normalizeResourceName( r ) ) )
565                    {
566                        Object modelObject = this.unmarshalModelObject( fileInfo.getContents() );
567
568                        if ( modelObject instanceof JAXBElement<?> )
569                        {
570                            modelObject = ( (JAXBElement<?>) modelObject ).getValue();
571                        }
572                        if ( modelObject instanceof Modules )
573                        {
574                            this.modules.getModule().addAll( ( (Modules) modelObject ).getModule() );
575                        }
576                        if ( modelObject instanceof Module )
577                        {
578                            this.modules.getModule().add( (Module) modelObject );
579                        }
580
581                        if ( this.getLogger() != null && this.getLogger().isDebugEnabled() )
582                        {
583                            this.getLogger().debug( LOG_PREFIX + Messages.getMessage(
584                                "processingModuleResource", name ) );
585
586                        }
587
588                        selected = false;
589                        break;
590                    }
591                }
592            }
593
594            if ( selected && this.modletResources != null )
595            {
596                for ( final String r : this.modletResources )
597                {
598                    if ( name.equals( normalizeResourceName( r ) ) )
599                    {
600                        Object modletObject = this.unmarshalModletObject( fileInfo.getContents() );
601
602                        if ( modletObject instanceof JAXBElement<?> )
603                        {
604                            modletObject = ( (JAXBElement<?>) modletObject ).getValue();
605                        }
606                        if ( modletObject instanceof Modlets )
607                        {
608                            this.modlets.getModlet().addAll( ( (Modlets) modletObject ).getModlet() );
609                        }
610                        if ( modletObject instanceof Modlet )
611                        {
612                            this.modlets.getModlet().add( (Modlet) modletObject );
613                        }
614
615                        if ( this.getLogger() != null && this.getLogger().isDebugEnabled() )
616                        {
617                            this.getLogger().debug( LOG_PREFIX + Messages.getMessage(
618                                "processingModletResource", name ) );
619
620                        }
621
622                        selected = false;
623                        break;
624                    }
625                }
626            }
627
628            if ( selected && ( name.equals( normalizeResourceName( this.modletResource ) )
629                               || name.equals( normalizeResourceName( this.moduleResource ) ) ) )
630            {
631                if ( this.getLogger() != null && this.getLogger().isWarnEnabled() )
632                {
633                    this.getLogger().warn( LOG_PREFIX + Messages.getMessage( "overridingResource", name ) );
634                }
635
636                selected = false;
637            }
638
639            return selected;
640        }
641        catch ( final InstantiationException e )
642        {
643            // JDK: As of JDK 6, "new IOException( message, cause )".
644            throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
645        }
646        catch ( final JAXBException e )
647        {
648            String message = Messages.getMessage( e );
649            if ( message == null && e.getLinkedException() != null )
650            {
651                message = Messages.getMessage( e.getLinkedException() );
652            }
653
654            // JDK: As of JDK 6, "new IOException( message, cause )".
655            throw (IOException) new IOException( message ).initCause( e );
656        }
657        catch ( final ModelException e )
658        {
659            // JDK: As of JDK 6, "new IOException( message, cause )".
660            throw (IOException) new IOException( Messages.getMessage( e ) ).initCause( e );
661        }
662    }
663
664    /**
665     * Creates an {@code URL} for a given resource location.
666     * <p>
667     * This method first searches the class loader of the class for a single resource matching {@code location}. If
668     * such a resource is found, the URL of that resource is returned. If no such resource is found, an attempt is made
669     * to parse the given location to an URL. On successful parsing, that URL is returned. Failing that, the given
670     * location is interpreted as a file name. If that file is found, the URL of that file is returned. Otherwise an
671     * {@code IOException} is thrown.
672     * </p>
673     *
674     * @param location The location to create an {@code URL} from.
675     *
676     * @return An {@code URL} for {@code location}.
677     *
678     * @throws NullPointerException if {@code location} is {@code null}.
679     * @throws IOException if creating an URL fails.
680     */
681    protected URL getResource( final String location ) throws IOException
682    {
683        if ( location == null )
684        {
685            throw new NullPointerException( "location" );
686        }
687
688        try
689        {
690            String absolute = location;
691            if ( !absolute.startsWith( "/" ) )
692            {
693                absolute = "/" + location;
694            }
695
696            URL resource = this.getClass().getResource( absolute );
697            if ( resource == null )
698            {
699                try
700                {
701                    resource = new URL( location );
702                }
703                catch ( final MalformedURLException e )
704                {
705                    if ( this.getLogger() != null && this.getLogger().isDebugEnabled() )
706                    {
707                        this.getLogger().debug( Messages.getMessage( e ), e );
708                    }
709
710                    resource = null;
711                }
712            }
713
714            if ( resource == null )
715            {
716                final File f = new File( location );
717
718                if ( f.isFile() )
719                {
720                    resource = f.toURI().toURL();
721                }
722            }
723
724            if ( resource == null )
725            {
726                throw new IOException( Messages.getMessage( "resourceNotFound", location ) );
727            }
728
729            return resource;
730        }
731        catch ( final MalformedURLException e )
732        {
733            String m = Messages.getMessage( e );
734            m = m == null ? "" : " " + m;
735
736            // JDK: As of JDK 6, "new IOException( message, cause )".
737            throw (IOException) new IOException( Messages.getMessage(
738                "malformedLocation", location, m ) ).initCause( e );
739
740        }
741    }
742
743    private Object unmarshalModelObject( final InputStream in )
744        throws ModelException, JAXBException, InstantiationException
745    {
746        if ( in == null )
747        {
748            throw new NullPointerException( "in" );
749        }
750
751        if ( this.jomcUnmarshaller == null )
752        {
753            this.jomcUnmarshaller = this.createModelContext().createUnmarshaller( this.model );
754        }
755
756        return this.jomcUnmarshaller.unmarshal( in );
757    }
758
759    private void marshalModelObject( final JAXBElement<? extends ModelObject> element, final File file )
760        throws ModelException, JAXBException, InstantiationException
761    {
762        if ( element == null )
763        {
764            throw new NullPointerException( "element" );
765        }
766        if ( file == null )
767        {
768            throw new NullPointerException( "file" );
769        }
770
771        if ( this.jomcMarshaller == null )
772        {
773            final ModelContext modelContext = this.createModelContext();
774            this.jomcMarshaller = modelContext.createMarshaller( this.model );
775            this.jomcMarshaller.setSchema( modelContext.createSchema( this.model ) );
776            this.jomcMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
777
778            if ( this.moduleEncoding != null )
779            {
780                this.jomcMarshaller.setProperty( Marshaller.JAXB_ENCODING, this.moduleEncoding );
781            }
782        }
783
784        this.jomcMarshaller.marshal( element, file );
785    }
786
787    private <T> JAXBElement<T> transformModelObject( final JAXBElement<? extends ModelObject> element,
788                                                     final Class<T> boundType )
789        throws ModelException, TransformerException, JAXBException, IOException, URISyntaxException,
790               InstantiationException
791    {
792        if ( element == null )
793        {
794            throw new NullPointerException( "element" );
795        }
796        if ( !boundType.isInstance( element.getValue() ) )
797        {
798            throw new IllegalArgumentException( element.toString() );
799        }
800
801        @SuppressWarnings( "unchecked" )
802        JAXBElement<T> transformed = (JAXBElement<T>) element;
803
804        if ( this.modelObjectStylesheet != null )
805        {
806            final Transformer transformer = TransformerFactory.newInstance().newTransformer(
807                new StreamSource( this.getResource( this.modelObjectStylesheet ).toURI().toASCIIString() ) );
808
809            final ModelContext modelContext = this.createModelContext();
810            final Marshaller marshaller = modelContext.createMarshaller( this.model );
811            final Unmarshaller unmarshaller = modelContext.createUnmarshaller( this.model );
812            final JAXBSource source = new JAXBSource( marshaller, element );
813            final JAXBResult result = new JAXBResult( unmarshaller );
814
815            for ( final Map.Entry<Object, Object> e : System.getProperties().entrySet() )
816            {
817                transformer.setParameter( e.getKey().toString(), e.getValue() );
818            }
819
820            transformer.transform( source, result );
821
822            if ( result.getResult() instanceof JAXBElement<?>
823                     && boundType.isInstance( ( (JAXBElement<?>) result.getResult() ).getValue() ) )
824            {
825                @SuppressWarnings( "unchecked" ) final JAXBElement<T> e = (JAXBElement<T>) result.getResult();
826                transformed = e;
827            }
828            else
829            {
830                throw new ModelException( Messages.getMessage(
831                    "illegalModuleTransformationResult", this.modelObjectStylesheet ) );
832
833            }
834        }
835
836        return transformed;
837    }
838
839    private Object unmarshalModletObject( final InputStream in )
840        throws ModelException, JAXBException, InstantiationException
841    {
842        if ( in == null )
843        {
844            throw new NullPointerException( "in" );
845        }
846
847        if ( this.modletUnmarshaller == null )
848        {
849            this.modletUnmarshaller = this.createModelContext().createUnmarshaller( ModletObject.MODEL_PUBLIC_ID );
850        }
851
852        return this.modletUnmarshaller.unmarshal( in );
853    }
854
855    private void marshalModletObject( final JAXBElement<? extends ModletObject> element, final File file )
856        throws ModelException, JAXBException, InstantiationException
857    {
858        if ( element == null )
859        {
860            throw new NullPointerException( "element" );
861        }
862        if ( file == null )
863        {
864            throw new NullPointerException( "file" );
865        }
866
867        if ( this.modletMarshaller == null )
868        {
869            final ModelContext modletContext = this.createModelContext();
870            this.modletMarshaller = modletContext.createMarshaller( ModletObject.MODEL_PUBLIC_ID );
871            this.modletMarshaller.setSchema( modletContext.createSchema( ModletObject.MODEL_PUBLIC_ID ) );
872            this.modletMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
873
874            if ( this.modletEncoding != null )
875            {
876                this.modletMarshaller.setProperty( Marshaller.JAXB_ENCODING, this.modletEncoding );
877            }
878        }
879
880        this.modletMarshaller.marshal( element, file );
881    }
882
883    private <T> JAXBElement<T> transformModletObject( final JAXBElement<? extends ModletObject> element,
884                                                      final Class<T> boundType )
885        throws ModelException, TransformerException, JAXBException, IOException, URISyntaxException,
886               InstantiationException
887    {
888        if ( element == null )
889        {
890            throw new NullPointerException( "element" );
891        }
892        if ( !boundType.isInstance( element.getValue() ) )
893        {
894            throw new IllegalArgumentException( element.toString() );
895        }
896
897        @SuppressWarnings( "unchecked" )
898        JAXBElement<T> transformed = (JAXBElement<T>) element;
899
900        if ( this.modletObjectStylesheet != null )
901        {
902            final Transformer transformer = TransformerFactory.newInstance().newTransformer(
903                new StreamSource( this.getResource( this.modletObjectStylesheet ).toURI().toASCIIString() ) );
904
905            final ModelContext modletContext = this.createModelContext();
906            final Marshaller marshaller = modletContext.createMarshaller( ModletObject.MODEL_PUBLIC_ID );
907            final Unmarshaller unmarshaller = modletContext.createUnmarshaller( ModletObject.MODEL_PUBLIC_ID );
908            final JAXBSource source = new JAXBSource( marshaller, element );
909            final JAXBResult result = new JAXBResult( unmarshaller );
910
911            for ( final Map.Entry<Object, Object> e : System.getProperties().entrySet() )
912            {
913                transformer.setParameter( e.getKey().toString(), e.getValue() );
914            }
915
916            transformer.transform( source, result );
917
918            if ( result.getResult() instanceof JAXBElement<?>
919                     && boundType.isInstance( ( (JAXBElement<?>) result.getResult() ).getValue() ) )
920            {
921                @SuppressWarnings( "unchecked" ) final JAXBElement<T> e = (JAXBElement<T>) result.getResult();
922                transformed = e;
923            }
924            else
925            {
926                throw new ModelException( Messages.getMessage(
927                    "illegalModletTransformationResult", this.modletObjectStylesheet ) );
928
929            }
930        }
931
932        return transformed;
933    }
934
935    private static String normalizeResourceName( final String name )
936    {
937        String normalized = name;
938
939        if ( normalized != null )
940        {
941            normalized = normalized.replace( '\\', '/' );
942
943            if ( normalized.startsWith( "/" ) )
944            {
945                normalized = normalized.substring( 1 );
946            }
947
948            if ( normalized.endsWith( "/" ) )
949            {
950                normalized = normalized.substring( 0, normalized.length() );
951            }
952        }
953
954        return normalized;
955    }
956
957    private ModelContext createModelContext() throws ModelException, InstantiationException
958    {
959        final ModelContextFactory modelContextFactory;
960        if ( this.modelContextFactoryClassName != null )
961        {
962            modelContextFactory = ModelContextFactory.newInstance( this.modelContextFactoryClassName );
963        }
964        else
965        {
966            modelContextFactory = ModelContextFactory.newInstance();
967        }
968
969        final ModelContext modelContext = modelContextFactory.newModelContext();
970        modelContext.setModletSchemaSystemId( this.modletSchemaSystemId );
971
972        if ( this.providerLocation != null )
973        {
974            modelContext.setAttribute( DefaultModelContext.PROVIDER_LOCATION_ATTRIBUTE_NAME, this.providerLocation );
975        }
976
977        if ( this.platformProviderLocation != null )
978        {
979            modelContext.setAttribute( DefaultModelContext.PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME,
980                                       this.platformProviderLocation );
981
982        }
983
984        if ( this.modletLocation != null )
985        {
986            modelContext.setAttribute( DefaultModletProvider.MODLET_LOCATION_ATTRIBUTE_NAME, this.modletLocation );
987        }
988
989        if ( this.modelContextAttributes != null )
990        {
991            for ( final ModelContextAttribute e : this.modelContextAttributes )
992            {
993                final Object object = e.getObject( modelContext );
994
995                if ( object != null )
996                {
997                    modelContext.setAttribute( e.getKey(), object );
998                }
999                else
1000                {
1001                    modelContext.clearAttribute( e.getKey() );
1002                }
1003            }
1004        }
1005
1006        return modelContext;
1007    }
1008
1009}