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