001/* 002 * Copyright 2002-2019 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 * https://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 017package org.springframework.beans.factory.xml; 018 019import java.io.File; 020import java.io.IOException; 021import java.net.URL; 022import java.net.URLDecoder; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.xml.sax.InputSource; 027import org.xml.sax.SAXException; 028 029import org.springframework.core.io.Resource; 030import org.springframework.core.io.ResourceLoader; 031 032/** 033 * {@code EntityResolver} implementation that tries to resolve entity references 034 * through a {@link org.springframework.core.io.ResourceLoader} (usually, 035 * relative to the resource base of an {@code ApplicationContext}), if applicable. 036 * Extends {@link DelegatingEntityResolver} to also provide DTD and XSD lookup. 037 * 038 * <p>Allows to use standard XML entities to include XML snippets into an 039 * application context definition, for example to split a large XML file 040 * into various modules. The include paths can be relative to the 041 * application context's resource base as usual, instead of relative 042 * to the JVM working directory (the XML parser's default). 043 * 044 * <p>Note: In addition to relative paths, every URL that specifies a 045 * file in the current system root, i.e. the JVM working directory, 046 * will be interpreted relative to the application context too. 047 * 048 * @author Juergen Hoeller 049 * @since 31.07.2003 050 * @see org.springframework.core.io.ResourceLoader 051 * @see org.springframework.context.ApplicationContext 052 */ 053public class ResourceEntityResolver extends DelegatingEntityResolver { 054 055 private static final Log logger = LogFactory.getLog(ResourceEntityResolver.class); 056 057 private final ResourceLoader resourceLoader; 058 059 060 /** 061 * Create a ResourceEntityResolver for the specified ResourceLoader 062 * (usually, an ApplicationContext). 063 * @param resourceLoader the ResourceLoader (or ApplicationContext) 064 * to load XML entity includes with 065 */ 066 public ResourceEntityResolver(ResourceLoader resourceLoader) { 067 super(resourceLoader.getClassLoader()); 068 this.resourceLoader = resourceLoader; 069 } 070 071 072 @Override 073 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 074 InputSource source = super.resolveEntity(publicId, systemId); 075 076 if (source == null && systemId != null) { 077 String resourcePath = null; 078 try { 079 String decodedSystemId = URLDecoder.decode(systemId, "UTF-8"); 080 String givenUrl = new URL(decodedSystemId).toString(); 081 String systemRootUrl = new File("").toURI().toURL().toString(); 082 // Try relative to resource base if currently in system root. 083 if (givenUrl.startsWith(systemRootUrl)) { 084 resourcePath = givenUrl.substring(systemRootUrl.length()); 085 } 086 } 087 catch (Exception ex) { 088 // Typically a MalformedURLException or AccessControlException. 089 if (logger.isDebugEnabled()) { 090 logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex); 091 } 092 // No URL (or no resolvable URL) -> try relative to resource base. 093 resourcePath = systemId; 094 } 095 if (resourcePath != null) { 096 if (logger.isTraceEnabled()) { 097 logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]"); 098 } 099 Resource resource = this.resourceLoader.getResource(resourcePath); 100 source = new InputSource(resource.getInputStream()); 101 source.setPublicId(publicId); 102 source.setSystemId(systemId); 103 if (logger.isDebugEnabled()) { 104 logger.debug("Found XML entity [" + systemId + "]: " + resource); 105 } 106 } 107 else if (systemId.endsWith(DTD_SUFFIX) || systemId.endsWith(XSD_SUFFIX)) { 108 // External dtd/xsd lookup via https even for canonical http declaration 109 String url = systemId; 110 if (url.startsWith("http:")) { 111 url = "https:" + url.substring(5); 112 } 113 try { 114 source = new InputSource(new URL(url).openStream()); 115 source.setPublicId(publicId); 116 source.setSystemId(systemId); 117 } 118 catch (IOException ex) { 119 if (logger.isDebugEnabled()) { 120 logger.debug("Could not resolve XML entity [" + systemId + "] through URL [" + url + "]", ex); 121 } 122 // Fall back to the parser's default behavior. 123 source = null; 124 } 125 } 126 } 127 128 return source; 129 } 130 131}