001/*
002 *  jDTAUS Core RI Entity Resolver
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.core.sax.ri.resolver;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.net.URI;
026import java.net.URISyntaxException;
027import java.net.URL;
028import java.util.Enumeration;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.Locale;
032import java.util.Map;
033import java.util.Set;
034import java.util.jar.Manifest;
035import org.jdtaus.core.container.ContainerFactory;
036import org.jdtaus.core.logging.spi.Logger;
037import org.xml.sax.EntityResolver;
038import org.xml.sax.InputSource;
039import org.xml.sax.SAXException;
040
041/**
042 * {@code EntityResolver} implementation resolving XML schemas from classpath
043 * resources.
044 *
045 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
046 * @version $JDTAUS: ClasspathEntityResolver.java 8743 2012-10-07 03:06:20Z schulte $
047 */
048public class ClasspathEntityResolver implements EntityResolver
049{
050    //--Constructors------------------------------------------------------------
051
052// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausConstructors
053    // This section is managed by jdtaus-container-mojo.
054
055    /** Standard implementation constructor <code>org.jdtaus.core.sax.ri.resolver.ClasspathEntityResolver</code>. */
056    public ClasspathEntityResolver()
057    {
058        super();
059    }
060
061// </editor-fold>//GEN-END:jdtausConstructors
062
063    //------------------------------------------------------------Constructors--
064    //--Dependencies------------------------------------------------------------
065
066// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
067    // This section is managed by jdtaus-container-mojo.
068
069    /**
070     * Gets the configured <code>Logger</code> implementation.
071     *
072     * @return The configured <code>Logger</code> implementation.
073     */
074    private Logger getLogger()
075    {
076        return (Logger) ContainerFactory.getContainer().
077            getDependency( this, "Logger" );
078
079    }
080
081    /**
082     * Gets the configured <code>Locale</code> implementation.
083     *
084     * @return The configured <code>Locale</code> implementation.
085     */
086    private Locale getLocale()
087    {
088        return (Locale) ContainerFactory.getContainer().
089            getDependency( this, "Locale" );
090
091    }
092
093// </editor-fold>//GEN-END:jdtausDependencies
094
095    //------------------------------------------------------------Dependencies--
096    //--Properties--------------------------------------------------------------
097
098// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
099    // This section is managed by jdtaus-container-mojo.
100
101    /**
102     * Gets the value of property <code>defaultSchemaExtensions</code>.
103     *
104     * @return Default extensions to match classpath resources with (separated by ',').
105     */
106    private java.lang.String getDefaultSchemaExtensions()
107    {
108        return (java.lang.String) ContainerFactory.getContainer().
109            getProperty( this, "defaultSchemaExtensions" );
110
111    }
112
113// </editor-fold>//GEN-END:jdtausProperties
114
115    //--------------------------------------------------------------Properties--
116    //--EntityResolver----------------------------------------------------------
117
118    public InputSource resolveEntity( final String publicId,
119                                      final String systemId )
120        throws SAXException, IOException
121    {
122        if ( systemId == null )
123        {
124            throw new NullPointerException( "systemId" );
125        }
126
127        InputSource schemaSource = null;
128
129        try
130        {
131            final URI systemUri = new URI( systemId );
132            String schemaName = systemUri.getPath();
133            if ( schemaName != null )
134            {
135                final int lastIndexOfSlash = schemaName.lastIndexOf( '/' );
136                if ( lastIndexOfSlash != -1 &&
137                     lastIndexOfSlash < schemaName.length() )
138                {
139                    schemaName =
140                        schemaName.substring( lastIndexOfSlash + 1 );
141
142                }
143
144                final URL[] urls = this.getSchemaUrls();
145                for ( int i = urls.length - 1; i >= 0; i-- )
146                {
147                    if ( urls[i].getPath().endsWith( schemaName ) )
148                    {
149                        schemaSource = new InputSource();
150                        schemaSource.setPublicId( publicId );
151                        schemaSource.setSystemId( urls[i].toExternalForm() );
152
153                        if ( this.getLogger().isDebugEnabled() )
154                        {
155                            this.getLogger().debug(
156                                this.getResolvedSystemIdMessage(
157                                this.getLocale(), systemId,
158                                schemaSource.getSystemId() ) );
159
160                        }
161
162                        break;
163                    }
164                }
165            }
166            else
167            {
168                this.getLogger().warn( this.getUnsupportedSystemIdUriMessage(
169                    this.getLocale(), systemId, systemUri.toASCIIString() ) );
170
171            }
172        }
173        catch ( final URISyntaxException e )
174        {
175            this.getLogger().warn( this.getUnsupportedSystemIdUriMessage(
176                this.getLocale(), systemId, e.getMessage() ) );
177
178            schemaSource = null;
179        }
180
181        return schemaSource;
182    }
183
184    //----------------------------------------------------------EntityResolver--
185    //--ClasspathEntityResolver-------------------------------------------------
186
187    /** Schema extensions. */
188    private String[] schemaExtensions;
189
190    /** URLs of all available classpath schema resources. */
191    private URL[] schemaUrls;
192
193    /**
194     * Creates a new {@code ClasspathEntityResolver} instance taking the
195     * extensions to match classpath resouces with.
196     *
197     * @param schemaExtensions extensions to match classpath resouces with.
198     */
199    public ClasspathEntityResolver( final String[] schemaExtensions )
200    {
201        if ( schemaExtensions != null && schemaExtensions.length > 0 )
202        {
203            this.schemaExtensions = schemaExtensions;
204        }
205    }
206
207    /**
208     * Gets the value of property {@code schemaExtensions}.
209     *
210     * @return extensions to match classpath resources with.
211     */
212    private String[] getSchemaExtensions()
213    {
214        if ( this.schemaExtensions == null )
215        {
216            this.schemaExtensions =
217                this.getDefaultSchemaExtensions().split( "," );
218
219        }
220
221        return this.schemaExtensions;
222    }
223
224    /**
225     * Gets URLs of all available classpath schema resources.
226     *
227     * @return URLs of all available classpath schema resources.
228     */
229    private URL[] getSchemaUrls()
230    {
231        if ( this.schemaUrls == null )
232        {
233            try
234            {
235                this.schemaUrls = this.getSchemaResources();
236            }
237            catch ( final IOException e )
238            {
239                this.getLogger().error( this.getDisabledMessage(
240                    this.getLocale(), e.getMessage() ) );
241
242                this.schemaUrls = null;
243            }
244            catch ( final URISyntaxException e )
245            {
246                this.getLogger().error( this.getDisabledMessage(
247                    this.getLocale(), e.getMessage() ) );
248
249                this.schemaUrls = null;
250            }
251        }
252
253        return this.schemaUrls != null ? this.schemaUrls : new URL[ 0 ];
254    }
255
256    /**
257     * Searches all available {@code META-INF/MANIFEST.MF} resources for
258     * entries whose name end with one of the extensions specified by
259     * property {@code schemaExtensions}.
260     *
261     * @return URLs of any matching resources.
262     *
263     * @throws IOException if reading or parsing fails.
264     * @throws URISyntaxException if creating a schema URI fails.
265     */
266    private URL[] getSchemaResources() throws IOException, URISyntaxException
267    {
268        final ClassLoader cl = this.getClass().getClassLoader();
269        final Set/*<URI>*/ schemaResources = new HashSet();
270
271        for ( final Enumeration e = cl.getResources( "META-INF/MANIFEST.MF" );
272              e.hasMoreElements(); )
273        {
274            final String[] extensions = this.getSchemaExtensions();
275            final URL manifestUrl = (URL) e.nextElement();
276            final String externalForm = manifestUrl.toExternalForm();
277            final String baseUrl =
278                externalForm.substring( 0, externalForm.indexOf( "META-INF" ) );
279
280            final InputStream manifestStream = manifestUrl.openStream();
281            final Manifest mf = new Manifest( manifestStream );
282
283            manifestStream.close();
284
285            for ( final Iterator it = mf.getEntries().entrySet().iterator();
286                  it.hasNext(); )
287            {
288                final Map.Entry entry = (Map.Entry) it.next();
289                for ( int i = extensions.length - 1; i >= 0; i-- )
290                {
291                    if ( entry.getKey().toString().toLowerCase().
292                        endsWith( '.' + extensions[i].toLowerCase() ) )
293                    {
294                        final URL schemaUrl =
295                            new URL( baseUrl + entry.getKey().toString() );
296
297                        schemaResources.add( new URI( schemaUrl.toString() ) );
298
299                        if ( this.getLogger().isDebugEnabled() )
300                        {
301                            this.getLogger().debug(
302                                this.getCandidateSchemaMessage(
303                                this.getLocale(),
304                                schemaUrl.toExternalForm() ) );
305
306                        }
307                    }
308                }
309            }
310        }
311
312        final URL[] urls = new URL[ schemaResources.size() ];
313        final Iterator it = schemaResources.iterator();
314        for ( int i = 0; it.hasNext(); i++ )
315        {
316            urls[i] = ( (URI) it.next() ).toURL();
317        }
318
319        return urls;
320    }
321
322    //-------------------------------------------------ClasspathEntityResolver--
323    //--Messages----------------------------------------------------------------
324
325// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
326    // This section is managed by jdtaus-container-mojo.
327
328    /**
329     * Gets the text of message <code>candidateSchema</code>.
330     * <blockquote><pre>{0} zur Liste der Schema-Kandidaten hinzugefügt.</pre></blockquote>
331     * <blockquote><pre>Added {0} to the list of candidate schema resources.</pre></blockquote>
332     *
333     * @param locale The locale of the message instance to return.
334     * @param schemaLocation Location of the candidate schema.
335     *
336     * @return Information about a schema added to the list of candidate schemas.
337     */
338    private String getCandidateSchemaMessage( final Locale locale,
339            final java.lang.String schemaLocation )
340    {
341        return ContainerFactory.getContainer().
342            getMessage( this, "candidateSchema", locale,
343                new Object[]
344                {
345                    schemaLocation
346                });
347
348    }
349
350    /**
351     * Gets the text of message <code>resolvedSystemId</code>.
352     * <blockquote><pre>{0}
353     *        -> {1}</pre></blockquote>
354     * <blockquote><pre>{0}
355     *        -> {1}</pre></blockquote>
356     *
357     * @param locale The locale of the message instance to return.
358     * @param systemId System id of the schema.
359     * @param resolvedSystemId Resolved system id of the schema.
360     *
361     * @return Information about a resolved schema.
362     */
363    private String getResolvedSystemIdMessage( final Locale locale,
364            final java.lang.String systemId,
365            final java.lang.String resolvedSystemId )
366    {
367        return ContainerFactory.getContainer().
368            getMessage( this, "resolvedSystemId", locale,
369                new Object[]
370                {
371                    systemId,
372                    resolvedSystemId
373                });
374
375    }
376
377    /**
378     * Gets the text of message <code>unsupportedSystemIdUri</code>.
379     * <blockquote><pre>Nicht unterstützter System-ID URI "{0}". {1}</pre></blockquote>
380     * <blockquote><pre>Unsupported system id URI "{0}". {1}</pre></blockquote>
381     *
382     * @param locale The locale of the message instance to return.
383     * @param systemIdUri Unsupported system id URI.
384     * @param cause Cause the URI is not supported.
385     *
386     * @return Information about an unsupported system id URI.
387     */
388    private String getUnsupportedSystemIdUriMessage( final Locale locale,
389            final java.lang.String systemIdUri,
390            final java.lang.String cause )
391    {
392        return ContainerFactory.getContainer().
393            getMessage( this, "unsupportedSystemIdUri", locale,
394                new Object[]
395                {
396                    systemIdUri,
397                    cause
398                });
399
400    }
401
402    /**
403     * Gets the text of message <code>disabled</code>.
404     * <blockquote><pre>Klassenpfad-Resourcen konnten nicht verarbeitet werden. Resolver wurde deaktiviert ! {0}</pre></blockquote>
405     * <blockquote><pre>Could not process classpath resources. Resolver is disabled ! {0}</pre></blockquote>
406     *
407     * @param locale The locale of the message instance to return.
408     * @param cause Cause the resolver is disabled.
409     *
410     * @return .
411     */
412    private String getDisabledMessage( final Locale locale,
413            final java.lang.String cause )
414    {
415        return ContainerFactory.getContainer().
416            getMessage( this, "disabled", locale,
417                new Object[]
418                {
419                    cause
420                });
421
422    }
423
424// </editor-fold>//GEN-END:jdtausMessages
425
426    //----------------------------------------------------------------Messages--
427}