001/* 002 * Copyright 2002-2018 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.IOException; 020import java.util.Map; 021import java.util.Properties; 022import java.util.concurrent.ConcurrentHashMap; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.beans.BeanUtils; 028import org.springframework.beans.FatalBeanException; 029import org.springframework.core.io.support.PropertiesLoaderUtils; 030import org.springframework.util.Assert; 031import org.springframework.util.ClassUtils; 032import org.springframework.util.CollectionUtils; 033 034/** 035 * Default implementation of the {@link NamespaceHandlerResolver} interface. 036 * Resolves namespace URIs to implementation classes based on the mappings 037 * contained in mapping file. 038 * 039 * <p>By default, this implementation looks for the mapping file at 040 * {@code META-INF/spring.handlers}, but this can be changed using the 041 * {@link #DefaultNamespaceHandlerResolver(ClassLoader, String)} constructor. 042 * 043 * @author Rob Harrop 044 * @author Juergen Hoeller 045 * @since 2.0 046 * @see NamespaceHandler 047 * @see DefaultBeanDefinitionDocumentReader 048 */ 049public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver { 050 051 /** 052 * The location to look for the mapping files. Can be present in multiple JAR files. 053 */ 054 public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"; 055 056 057 /** Logger available to subclasses */ 058 protected final Log logger = LogFactory.getLog(getClass()); 059 060 /** ClassLoader to use for NamespaceHandler classes */ 061 private final ClassLoader classLoader; 062 063 /** Resource location to search for */ 064 private final String handlerMappingsLocation; 065 066 /** Stores the mappings from namespace URI to NamespaceHandler class name / instance */ 067 private volatile Map<String, Object> handlerMappings; 068 069 070 /** 071 * Create a new {@code DefaultNamespaceHandlerResolver} using the 072 * default mapping file location. 073 * <p>This constructor will result in the thread context ClassLoader being used 074 * to load resources. 075 * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION 076 */ 077 public DefaultNamespaceHandlerResolver() { 078 this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION); 079 } 080 081 /** 082 * Create a new {@code DefaultNamespaceHandlerResolver} using the 083 * default mapping file location. 084 * @param classLoader the {@link ClassLoader} instance used to load mapping resources 085 * (may be {@code null}, in which case the thread context ClassLoader will be used) 086 * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION 087 */ 088 public DefaultNamespaceHandlerResolver(ClassLoader classLoader) { 089 this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION); 090 } 091 092 /** 093 * Create a new {@code DefaultNamespaceHandlerResolver} using the 094 * supplied mapping file location. 095 * @param classLoader the {@link ClassLoader} instance used to load mapping resources 096 * may be {@code null}, in which case the thread context ClassLoader will be used) 097 * @param handlerMappingsLocation the mapping file location 098 */ 099 public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) { 100 Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null"); 101 this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); 102 this.handlerMappingsLocation = handlerMappingsLocation; 103 } 104 105 106 /** 107 * Locate the {@link NamespaceHandler} for the supplied namespace URI 108 * from the configured mappings. 109 * @param namespaceUri the relevant namespace URI 110 * @return the located {@link NamespaceHandler}, or {@code null} if none found 111 */ 112 @Override 113 public NamespaceHandler resolve(String namespaceUri) { 114 Map<String, Object> handlerMappings = getHandlerMappings(); 115 Object handlerOrClassName = handlerMappings.get(namespaceUri); 116 if (handlerOrClassName == null) { 117 return null; 118 } 119 else if (handlerOrClassName instanceof NamespaceHandler) { 120 return (NamespaceHandler) handlerOrClassName; 121 } 122 else { 123 String className = (String) handlerOrClassName; 124 try { 125 Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); 126 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { 127 throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + 128 "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); 129 } 130 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); 131 namespaceHandler.init(); 132 handlerMappings.put(namespaceUri, namespaceHandler); 133 return namespaceHandler; 134 } 135 catch (ClassNotFoundException ex) { 136 throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + 137 namespaceUri + "] not found", ex); 138 } 139 catch (LinkageError err) { 140 throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + 141 namespaceUri + "]: problem with handler class file or dependent class", err); 142 } 143 } 144 } 145 146 /** 147 * Load the specified NamespaceHandler mappings lazily. 148 */ 149 private Map<String, Object> getHandlerMappings() { 150 Map<String, Object> handlerMappings = this.handlerMappings; 151 if (handlerMappings == null) { 152 synchronized (this) { 153 handlerMappings = this.handlerMappings; 154 if (handlerMappings == null) { 155 if (logger.isDebugEnabled()) { 156 logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]"); 157 } 158 try { 159 Properties mappings = 160 PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); 161 if (logger.isDebugEnabled()) { 162 logger.debug("Loaded NamespaceHandler mappings: " + mappings); 163 } 164 handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size()); 165 CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); 166 this.handlerMappings = handlerMappings; 167 } 168 catch (IOException ex) { 169 throw new IllegalStateException( 170 "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex); 171 } 172 } 173 } 174 } 175 return handlerMappings; 176 } 177 178 179 @Override 180 public String toString() { 181 return "NamespaceHandlerResolver using mappings " + getHandlerMappings(); 182 } 183 184}