001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package org.apache.geronimo.deployment.xml;
019    
020    import java.io.BufferedInputStream;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.net.MalformedURLException;
024    import java.net.URI;
025    import java.util.Vector;
026    
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    import org.apache.geronimo.gbean.GBeanInfo;
030    import org.apache.geronimo.gbean.GBeanInfoBuilder;
031    import org.apache.xml.resolver.Catalog;
032    import org.apache.xml.resolver.CatalogEntry;
033    import org.apache.xml.resolver.CatalogException;
034    import org.apache.xml.resolver.CatalogManager;
035    import org.xml.sax.EntityResolver;
036    import org.xml.sax.InputSource;
037    import org.xml.sax.SAXException;
038    
039    /**
040     * Implementation of EntityResolver that looks to the local filesystem.
041     *
042     * The implementation tries to resolve an entity via the following steps:
043     *
044     * <ul>
045     *   <li>using a catalog file</li>
046     *   <li>using a local repository</li>
047     *   <li>using JAR files in Classpath</li>
048     * </ul>
049     *
050     * The catalog resolving is based on the OASIS XML Catalog Standard.
051     * OASIS seems to move it around.  Try
052     * http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=entity
053     * and the list of documents currently at
054     * http://www.oasis-open.org/committees/documents.php?wg_abbrev=entity
055     * An older version may be at
056     * http://www.oasis-open.org/committees/entity/archives/spec-2001-08-01.html
057     * and see http://www.oasis-open.org/html/a401.htm
058     *
059     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
060     */
061    public class LocalEntityResolver implements EntityResolver {
062        /**
063         * used Logger
064         */
065        private static final Log log = LogFactory.getLog(LocalEntityResolver.class);
066    
067        /**
068         * The used Catalog Manager
069         */
070        private final CatalogManager manager = new CatalogManager();
071    
072        /**
073         * the XML Catalog
074         */
075        private Catalog catalog = null;
076    
077        /**
078         * the URI of the catalog file
079         */
080        private URI catalogFileURI = null;
081    
082        /**
083         * Local Repository where DTDs and Schemas are located
084         */
085        private URI localRepositoryURI = null;
086    
087        /**
088         * Flag indicating if this resolver may return null to signal
089         * the parser to open a regular URI connection to the system
090         * identifier. Otherwise an exception is thrown.
091         */
092        private boolean failOnUnresolvable = false;
093    
094    
095        public LocalEntityResolver(URI catalogFileURI, URI localRepositoryURI, boolean failOnUnresolvable) {
096            this.catalogFileURI = catalogFileURI;
097            setLocalRepositoryURI(localRepositoryURI);
098            setFailOnUnresolvable(failOnUnresolvable);
099            init();
100        }
101    
102        /**
103         * Sets the setFailOnUnresolvable flag.
104         *
105         * @param b value (true means that a SAXException is thrown
106         * if the entity could not be resolved)
107         */
108        public void setFailOnUnresolvable(final boolean b) {
109            failOnUnresolvable = b;
110        }
111    
112        public boolean isFailOnUnresolvable() {
113            return failOnUnresolvable;
114        }
115    
116        public void setCatalogFileURI(final URI catalogFileURI) {
117            this.catalogFileURI = catalogFileURI;
118            init();
119        }
120    
121        public URI getCatalogFileURI() {
122            return this.catalogFileURI;
123        }
124    
125        public URI getLocalRepositoryURI() {
126            return localRepositoryURI;
127        }
128    
129        public void setLocalRepositoryURI(URI string) {
130            localRepositoryURI = string;
131        }
132    
133        public void addPublicMapping(final String publicId, final String uri) {
134    
135            Vector args = new Vector();
136            args.add(publicId);
137            args.add(uri);
138    
139            addEntry("PUBLIC", args);
140    
141        }
142    
143        public void addSystemMapping(final String systemId, final String uri) {
144    
145            Vector args = new Vector();
146            args.add(systemId);
147            args.add(uri);
148    
149            addEntry("SYSTEM", args);
150    
151        }
152    
153        /**
154         * Attempt to resolve the entity based on the supplied publicId and systemId.
155         * First the catalog is queried with both publicId and systemId.
156         * Then the local repository is queried with the file name part of the systemId
157         * Then the classpath is queried with  the file name part of the systemId.
158         *
159         * Then, if failOnUnresolvable is true, an exception is thrown: otherwise null is returned.
160         * @param publicId
161         * @param systemId
162         * @return
163         * @throws SAXException
164         * @throws IOException
165         */
166        public InputSource resolveEntity(
167                final String publicId,
168                final String systemId)
169                throws SAXException, IOException {
170    
171            if (log.isTraceEnabled()) {
172                log.trace("start resolving for " + entityMessageString(publicId, systemId));
173            }
174    
175            InputSource source = resolveWithCatalog(publicId, systemId);
176            if (source != null) {
177                return source;
178            }
179    
180            source = resolveWithRepository(publicId, systemId);
181            if (source != null) {
182                return source;
183            }
184    
185            source = resolveWithClasspath(publicId, systemId);
186            if (source != null) {
187                return source;
188            }
189    
190            String message = "could not resolve " + entityMessageString(publicId, systemId);
191    
192            if (failOnUnresolvable) {
193                throw new SAXException(message);
194            } else {
195                log.debug(message);
196            }
197    
198            return null;
199        }
200    
201        /**
202         * Try to resolve using the catalog file
203         *
204         * @param publicId the PublicId
205         * @param systemId the SystemId
206         * @return InputSource if the entity could be resolved. null otherwise
207         * @throws MalformedURLException
208         * @throws IOException
209         */
210        InputSource resolveWithCatalog(
211                final String publicId,
212                final String systemId)
213                throws MalformedURLException, IOException {
214    
215            if (catalogFileURI == null) {
216                return null;
217            }
218    
219            String resolvedSystemId =
220                    catalog.resolvePublic(guaranteeNotNull(publicId), systemId);
221    
222            if (resolvedSystemId != null) {
223                if (log.isTraceEnabled()) {
224                    log.trace("resolved " + entityMessageString(publicId, systemId) + " using the catalog file. result: " + resolvedSystemId);
225                }
226                return new InputSource(resolvedSystemId);
227            }
228    
229            return null;
230        }
231    
232        /**
233         * Try to resolve using the local repository and only the supplied systemID filename.
234         * Any path in the systemID will be removed.
235         *
236         * @param publicId the PublicId
237         * @param systemId the SystemId
238         * @return InputSource if the entity could be resolved. null otherwise
239         */
240        InputSource resolveWithRepository(
241                final String publicId,
242                final String systemId) {
243    
244            if (localRepositoryURI == null) {
245                return null;
246            }
247    
248            String fileName = getSystemIdFileName(systemId);
249    
250            if (fileName == null) {
251                return null;
252            }
253    
254            InputStream inputStream = null;
255            URI resolvedSystemIDURI;
256            try {
257                resolvedSystemIDURI = localRepositoryURI.resolve(fileName);
258                inputStream = resolvedSystemIDURI.toURL().openStream();
259            } catch (IOException e) {
260                return null;
261            } catch (IllegalArgumentException e) {
262                //typically "uri is not absolute"
263                return null;
264            }
265            if (inputStream != null) {
266                if (log.isTraceEnabled()) {
267                    log.trace("resolved " + entityMessageString(publicId, systemId) + "with file relative to " + localRepositoryURI + resolvedSystemIDURI);
268                }
269                return new InputSource(inputStream);
270            } else {
271                return null;
272            }
273            /*
274            String resolvedSystemId = null;
275    
276            File file = new File(localRepositoryURI, fileName);
277            if (file.exists()) {
278                resolvedSystemId = file.getAbsolutePath();
279                if (log.isTraceEnabled()) {
280                    log.trace(
281                            "resolved "
282                            + entityMessageString(publicId, systemId)
283                            + "with file relative to "
284                            + localRepositoryURI
285                            + resolvedSystemId);
286                }
287                return new InputSource(resolvedSystemId);
288            }
289            return null;
290            */
291        }
292    
293        /**
294         * Try to resolve using the the classpath and only the supplied systemID.
295         * Any path in the systemID will be removed.
296         *
297         * @param publicId the PublicId
298         * @param systemId the SystemId
299         * @return InputSource if the entity could be resolved. null otherwise
300         */
301        InputSource resolveWithClasspath(
302                final String publicId,
303                final String systemId) {
304    
305            String fileName = getSystemIdFileName(systemId);
306    
307            if (fileName == null) {
308                return null;
309            }
310    
311            InputStream in =
312                    getClass().getClassLoader().getResourceAsStream(fileName);
313            if (in != null) {
314                if (log.isTraceEnabled()) {
315                    log.trace("resolved " + entityMessageString(publicId, systemId) + " via file found file on classpath: " + fileName);
316                }
317                InputSource is = new InputSource(new BufferedInputStream(in));
318                is.setSystemId(systemId);
319                return is;
320            }
321    
322            return null;
323        }
324    
325        /**
326         * Guarantees a not null value
327         */
328        private String guaranteeNotNull(final String string) {
329            return string != null ? string : "";
330        }
331    
332        /**
333         * Returns the SystemIds filename
334         *
335         * @param systemId SystemId
336         * @return filename
337         */
338        private String getSystemIdFileName(final String systemId) {
339    
340            if (systemId == null) {
341                return null;
342            }
343    
344            int indexBackSlash = systemId.lastIndexOf("\\");
345            int indexSlash = systemId.lastIndexOf("/");
346    
347            int index = Math.max(indexBackSlash, indexSlash);
348    
349            String fileName = systemId.substring(index + 1);
350            return fileName;
351        }
352    
353        /**
354         * Constructs a debugging message string
355         */
356        private String entityMessageString(
357                final String publicId,
358                final String systemId) {
359    
360            StringBuffer buffer = new StringBuffer("entity with publicId '");
361            buffer.append(publicId);
362            buffer.append("' and systemId '");
363            buffer.append(systemId);
364            buffer.append("'");
365            return buffer.toString();
366    
367        }
368    
369        /**
370         * Adds a new Entry to the catalog
371         */
372        private void addEntry(String type, Vector args) {
373            try {
374                CatalogEntry entry = new CatalogEntry(type, args);
375                catalog.addEntry(entry);
376            } catch (CatalogException e) {
377                throw new RuntimeException(e);
378            }
379        }
380    
381        /**
382         * Loads mappings from configuration file
383         */
384        private void init() {
385    
386            if (log.isDebugEnabled()) {
387                log.debug("init catalog file " + this.catalogFileURI);
388            }
389    
390            manager.setUseStaticCatalog(false);
391            manager.setCatalogFiles(this.catalogFileURI.toString());
392            manager.setIgnoreMissingProperties(true);
393            catalog = manager.getCatalog();
394        }
395    
396        public static final GBeanInfo GBEAN_INFO;
397    
398        static {
399            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("configurable local entity resolver", LocalEntityResolver.class);
400    
401            infoFactory.addAttribute("catalogFileURI", URI.class, true);
402            infoFactory.addAttribute("localRepositoryURI", URI.class, true);
403            infoFactory.addAttribute("failOnUnresolvable", boolean.class, true);
404    
405            infoFactory.addOperation("resolveEntity", new Class[]{String.class, String.class});
406            infoFactory.addOperation("addPublicMapping", new Class[]{String.class, String.class});
407            infoFactory.addOperation("addSystemMapping", new Class[]{String.class, String.class});
408    
409            infoFactory.setConstructor(new String[]{"catalogFileURI", "localRepositoryURI", "failOnUnresolvable"});
410    
411            GBEAN_INFO = infoFactory.getBeanInfo();
412        }
413    
414        public static GBeanInfo getGBeanInfo() {
415            return GBEAN_INFO;
416        }
417    
418    }