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