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