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 8641 2012-09-27 06:45:17Z 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 ( 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 ( IOException e )
238            {
239                this.getLogger().error( this.getDisabledMessage(
240                    this.getLocale(), e.getMessage() ) );
241
242                this.schemaUrls = null;
243            }
244        }
245
246        return this.schemaUrls != null ? this.schemaUrls : new URL[ 0 ];
247    }
248
249    /**
250     * Searches all available {@code META-INF/MANIFEST.MF} resources for
251     * entries whose name end with one of the extensions specified by
252     * property {@code schemaExtensions}.
253     *
254     * @return URLs of any matching resources.
255     *
256     * @throws IOException if reading or parsing fails.
257     */
258    private URL[] getSchemaResources() throws IOException
259    {
260        final ClassLoader cl = this.getClass().getClassLoader();
261        final Set schemaResources = new HashSet();
262
263        for ( Enumeration e = cl.getResources( "META-INF/MANIFEST.MF" );
264              e.hasMoreElements(); )
265        {
266            final String[] extensions = this.getSchemaExtensions();
267            final URL manifestUrl = (URL) e.nextElement();
268            final String externalForm = manifestUrl.toExternalForm();
269            final String baseUrl =
270                externalForm.substring( 0, externalForm.indexOf( "META-INF" ) );
271
272            final InputStream manifestStream = manifestUrl.openStream();
273            final Manifest mf = new Manifest( manifestStream );
274
275            manifestStream.close();
276
277            for ( Iterator it = mf.getEntries().entrySet().iterator();
278                  it.hasNext(); )
279            {
280                final Map.Entry entry = (Map.Entry) it.next();
281                for ( int i = extensions.length - 1; i >= 0; i-- )
282                {
283                    if ( entry.getKey().toString().toLowerCase().
284                        endsWith( '.' + extensions[i].toLowerCase() ) )
285                    {
286                        final URL schemaUrl =
287                            new URL( baseUrl + entry.getKey().toString() );
288
289                        schemaResources.add( schemaUrl );
290
291                        if ( this.getLogger().isDebugEnabled() )
292                        {
293                            this.getLogger().debug(
294                                this.getCandidateSchemaMessage(
295                                this.getLocale(),
296                                schemaUrl.toExternalForm() ) );
297
298                        }
299                    }
300                }
301            }
302        }
303
304        return (URL[]) schemaResources.toArray(
305            new URL[ schemaResources.size() ] );
306
307    }
308
309    //-------------------------------------------------ClasspathEntityResolver--
310    //--Messages----------------------------------------------------------------
311
312// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
313    // This section is managed by jdtaus-container-mojo.
314
315    /**
316     * Gets the text of message <code>candidateSchema</code>.
317     * <blockquote><pre>{0} zur Liste der Schema-Kandidaten hinzugefügt.</pre></blockquote>
318     * <blockquote><pre>Added {0} to the list of candidate schema resources.</pre></blockquote>
319     *
320     * @param locale The locale of the message instance to return.
321     * @param schemaLocation Location of the candidate schema.
322     *
323     * @return Information about a schema added to the list of candidate schemas.
324     */
325    private String getCandidateSchemaMessage( final Locale locale,
326            final java.lang.String schemaLocation )
327    {
328        return ContainerFactory.getContainer().
329            getMessage( this, "candidateSchema", locale,
330                new Object[]
331                {
332                    schemaLocation
333                });
334
335    }
336
337    /**
338     * Gets the text of message <code>resolvedSystemId</code>.
339     * <blockquote><pre>{0}
340     *        -> {1}</pre></blockquote>
341     * <blockquote><pre>{0}
342     *        -> {1}</pre></blockquote>
343     *
344     * @param locale The locale of the message instance to return.
345     * @param systemId System id of the schema.
346     * @param resolvedSystemId Resolved system id of the schema.
347     *
348     * @return Information about a resolved schema.
349     */
350    private String getResolvedSystemIdMessage( final Locale locale,
351            final java.lang.String systemId,
352            final java.lang.String resolvedSystemId )
353    {
354        return ContainerFactory.getContainer().
355            getMessage( this, "resolvedSystemId", locale,
356                new Object[]
357                {
358                    systemId,
359                    resolvedSystemId
360                });
361
362    }
363
364    /**
365     * Gets the text of message <code>unsupportedSystemIdUri</code>.
366     * <blockquote><pre>Nicht unterstützter System-ID URI "{0}". {1}</pre></blockquote>
367     * <blockquote><pre>Unsupported system id URI "{0}". {1}</pre></blockquote>
368     *
369     * @param locale The locale of the message instance to return.
370     * @param systemIdUri Unsupported system id URI.
371     * @param cause Cause the URI is not supported.
372     *
373     * @return Information about an unsupported system id URI.
374     */
375    private String getUnsupportedSystemIdUriMessage( final Locale locale,
376            final java.lang.String systemIdUri,
377            final java.lang.String cause )
378    {
379        return ContainerFactory.getContainer().
380            getMessage( this, "unsupportedSystemIdUri", locale,
381                new Object[]
382                {
383                    systemIdUri,
384                    cause
385                });
386
387    }
388
389    /**
390     * Gets the text of message <code>disabled</code>.
391     * <blockquote><pre>Klassenpfad-Resourcen konnten nicht verarbeitet werden. Resolver wurde deaktiviert ! {0}</pre></blockquote>
392     * <blockquote><pre>Could not process classpath resources. Resolver is disabled ! {0}</pre></blockquote>
393     *
394     * @param locale The locale of the message instance to return.
395     * @param cause Cause the resolver is disabled.
396     *
397     * @return .
398     */
399    private String getDisabledMessage( final Locale locale,
400            final java.lang.String cause )
401    {
402        return ContainerFactory.getContainer().
403            getMessage( this, "disabled", locale,
404                new Object[]
405                {
406                    cause
407                });
408
409    }
410
411// </editor-fold>//GEN-END:jdtausMessages
412
413    //----------------------------------------------------------------Messages--
414}