001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.beans.factory.xml;
018:
019: import java.io.IOException;
020: import java.util.Properties;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.xml.sax.EntityResolver;
025: import org.xml.sax.InputSource;
026:
027: import org.springframework.beans.FatalBeanException;
028: import org.springframework.core.io.ClassPathResource;
029: import org.springframework.core.io.Resource;
030: import org.springframework.core.io.support.PropertiesLoaderUtils;
031: import org.springframework.util.Assert;
032:
033: /**
034: * {@link EntityResolver} implementation that attempts to resolve schema URLs into
035: * local {@link ClassPathResource classpath resources} using a set of mappings files.
036: *
037: * <p>By default, this class will look for mapping files in the classpath using the pattern:
038: * <code>META-INF/spring.schemas</code> allowing for multiple files to exist on the
039: * classpath at any one time.
040: *
041: * The format of <code>META-INF/spring.schemas</code> is a properties
042: * file where each line should be of the form <code>systemId=schema-location</code>
043: * where <code>schema-location</code> should also be a schema file in the classpath.
044: * Since systemId is commonly a URL, one must be careful to escape any ':' characters
045: * which are treated as delimiters in properties files.
046: *
047: * <p>The pattern for the mapping files can be overidden using the
048: * {@link #PluggableSchemaResolver(ClassLoader, String)} constructor
049: *
050: * @author Rob Harrop
051: * @author Juergen Hoeller
052: * @since 2.0
053: */
054: public class PluggableSchemaResolver implements EntityResolver {
055:
056: /**
057: * The location of the file that defines schema mappings.
058: * Can be present in multiple JAR files.
059: */
060: public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
061:
062: private static final Log logger = LogFactory
063: .getLog(PluggableSchemaResolver.class);
064:
065: private final ClassLoader classLoader;
066:
067: private final String schemaMappingsLocation;
068:
069: /** Stores the mapping of schema URL -> local schema path */
070: private Properties schemaMappings;
071:
072: /**
073: * Loads the schema URL -> schema file location mappings using the default
074: * mapping file pattern "META-INF/spring.schemas".
075: * @param classLoader the ClassLoader to use for loading
076: * (can be <code>null</code>) to use the default ClassLoader)
077: * @see PropertiesLoaderUtils#loadAllProperties(String, ClassLoader)
078: */
079: public PluggableSchemaResolver(ClassLoader classLoader) {
080: this .classLoader = classLoader;
081: this .schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
082: }
083:
084: /**
085: * Loads the schema URL -> schema file location mappings using the given
086: * mapping file pattern.
087: * @param classLoader the ClassLoader to use for loading
088: * (can be <code>null</code>) to use the default ClassLoader)
089: * @param schemaMappingsLocation the location of the file that defines schema mappings
090: * (must not be empty)
091: * @see PropertiesLoaderUtils#loadAllProperties(String, ClassLoader)
092: */
093: public PluggableSchemaResolver(ClassLoader classLoader,
094: String schemaMappingsLocation) {
095: Assert.hasText(schemaMappingsLocation,
096: "'schemaMappingsLocation' must not be empty");
097: this .classLoader = classLoader;
098: this .schemaMappingsLocation = schemaMappingsLocation;
099: }
100:
101: public InputSource resolveEntity(String publicId, String systemId)
102: throws IOException {
103: if (logger.isTraceEnabled()) {
104: logger
105: .trace("Trying to resolve XML entity with public id ["
106: + publicId
107: + "] and system id ["
108: + systemId
109: + "]");
110: }
111: if (systemId != null) {
112: String resourceLocation = getSchemaMapping(systemId);
113: if (resourceLocation != null) {
114: Resource resource = new ClassPathResource(
115: resourceLocation, this .classLoader);
116: InputSource source = new InputSource(resource
117: .getInputStream());
118: source.setPublicId(publicId);
119: source.setSystemId(systemId);
120: if (logger.isDebugEnabled()) {
121: logger.debug("Found XML schema [" + systemId
122: + "] in classpath: " + resourceLocation);
123: }
124: return source;
125: }
126: }
127: return null;
128: }
129:
130: protected String getSchemaMapping(String systemId) {
131: if (this .schemaMappings == null) {
132: if (logger.isDebugEnabled()) {
133: logger.debug("Loading schema mappings from ["
134: + this .schemaMappingsLocation + "]");
135: }
136: try {
137: this .schemaMappings = PropertiesLoaderUtils
138: .loadAllProperties(this .schemaMappingsLocation,
139: this .classLoader);
140: if (logger.isDebugEnabled()) {
141: logger.debug("Loaded schema mappings: "
142: + this .schemaMappings);
143: }
144: } catch (IOException ex) {
145: throw new FatalBeanException(
146: "Unable to load schema mappings from location ["
147: + this .schemaMappingsLocation + "]", ex);
148: }
149: }
150: return this.schemaMappings.getProperty(systemId);
151: }
152:
153: }
|