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