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