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}