
infra.beans.factory.xml.DefaultNamespaceHandlerResolver Maven / Gradle / Ivy
/*
* Copyright 2017 - 2024 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [https://www.gnu.org/licenses/]
*/
package infra.beans.factory.xml;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import infra.beans.BeanUtils;
import infra.beans.FatalBeanException;
import infra.core.io.PropertiesUtils;
import infra.lang.Assert;
import infra.lang.Nullable;
import infra.logging.Logger;
import infra.logging.LoggerFactory;
import infra.util.ClassUtils;
import infra.util.CollectionUtils;
/**
* Default implementation of the {@link NamespaceHandlerResolver} interface.
* Resolves namespace URIs to implementation classes based on the mappings
* contained in mapping file.
*
* By default, this implementation looks for the mapping file at
* {@code META-INF/spring.handlers}, but this can be changed using the
* {@link #DefaultNamespaceHandlerResolver(ClassLoader, String)} constructor.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @see NamespaceHandler
* @see DefaultBeanDefinitionDocumentReader
* @since 4.0
*/
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
/**
* The location to look for the mapping files. Can be present in multiple JAR files.
*/
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
/** Logger available to subclasses. */
protected final Logger logger = LoggerFactory.getLogger(getClass());
/** ClassLoader to use for NamespaceHandler classes. */
@Nullable
private final ClassLoader classLoader;
/** Resource location to search for. */
private final String handlerMappingsLocation;
/** Stores the mappings from namespace URI to NamespaceHandler class name / instance. */
@Nullable
private volatile Map handlerMappings;
/**
* Create a new {@code DefaultNamespaceHandlerResolver} using the
* default mapping file location.
* This constructor will result in the thread context ClassLoader being used
* to load resources.
*
* @see #DEFAULT_HANDLER_MAPPINGS_LOCATION
*/
public DefaultNamespaceHandlerResolver() {
this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
/**
* Create a new {@code DefaultNamespaceHandlerResolver} using the
* default mapping file location.
*
* @param classLoader the {@link ClassLoader} instance used to load mapping resources
* (may be {@code null}, in which case the thread context ClassLoader will be used)
* @see #DEFAULT_HANDLER_MAPPINGS_LOCATION
*/
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
/**
* Create a new {@code DefaultNamespaceHandlerResolver} using the
* supplied mapping file location.
*
* @param classLoader the {@link ClassLoader} instance used to load mapping resources
* may be {@code null}, in which case the thread context ClassLoader will be used)
* @param handlerMappingsLocation the mapping file location
*/
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location is required");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
}
/**
* Locate the {@link NamespaceHandler} for the supplied namespace URI
* from the configured mappings.
*
* @param namespaceUri the relevant namespace URI
* @return the located {@link NamespaceHandler}, or {@code null} if none found
*/
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
Map handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = BeanUtils.newInstance(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
/**
* Load the specified NamespaceHandler mappings lazily.
*/
private Map getHandlerMappings() {
Map handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized(this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isTraceEnabled()) {
logger.trace("Loading NamespaceHandler mappings from [{}]", handlerMappingsLocation);
}
try {
Properties mappings = PropertiesUtils.loadAllProperties(handlerMappingsLocation, classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded NamespaceHandler mappings: {}", mappings);
}
handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}
@Override
public String toString() {
return "NamespaceHandlerResolver using mappings " + getHandlerMappings();
}
}