1 /**
2 *
3 * Copyright 2003-2004 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.geronimo.deployment.xml;
19
20 import java.io.BufferedInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.net.MalformedURLException;
24 import java.net.URI;
25 import java.util.Vector;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.geronimo.gbean.GBeanInfo;
30 import org.apache.geronimo.gbean.GBeanInfoBuilder;
31 import org.apache.xml.resolver.Catalog;
32 import org.apache.xml.resolver.CatalogEntry;
33 import org.apache.xml.resolver.CatalogException;
34 import org.apache.xml.resolver.CatalogManager;
35 import org.xml.sax.EntityResolver;
36 import org.xml.sax.InputSource;
37 import org.xml.sax.SAXException;
38
39 /**
40 * Implementation of EntityResolver that looks to the local filesystem.
41 *
42 * The implementation tries to resolve an entity via the following steps:
43 *
44 * <ul>
45 * <li>using a catalog file</li>
46 * <li>using a local repository</li>
47 * <li>using JAR files in Classpath</li>
48 * </ul>
49 *
50 * The catalog resolving is based on the OASIS XML Catalog Standard.
51 * OASIS seems to move it around. Try
52 * http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=entity
53 * and the list of documents currently at
54 * http://www.oasis-open.org/committees/documents.php?wg_abbrev=entity
55 * An older version may be at
56 * http://www.oasis-open.org/committees/entity/archives/spec-2001-08-01.html
57 * and see http://www.oasis-open.org/html/a401.htm
58 *
59 * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $
60 */
61 public class LocalEntityResolver implements EntityResolver {
62 /**
63 * used Logger
64 */
65 private static final Log log = LogFactory.getLog(LocalEntityResolver.class);
66
67 /**
68 * The used Catalog Manager
69 */
70 private final CatalogManager manager = new CatalogManager();
71
72 /**
73 * the XML Catalog
74 */
75 private Catalog catalog = null;
76
77 /**
78 * the URI of the catalog file
79 */
80 private URI catalogFileURI = null;
81
82 /**
83 * Local Repository where DTDs and Schemas are located
84 */
85 private URI localRepositoryURI = null;
86
87 /**
88 * Flag indicating if this resolver may return null to signal
89 * the parser to open a regular URI connection to the system
90 * identifier. Otherwise an exception is thrown.
91 */
92 private boolean failOnUnresolvable = false;
93
94
95 public LocalEntityResolver(URI catalogFileURI, URI localRepositoryURI, boolean failOnUnresolvable) {
96 this.catalogFileURI = catalogFileURI;
97 setLocalRepositoryURI(localRepositoryURI);
98 setFailOnUnresolvable(failOnUnresolvable);
99 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
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
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 }