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