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.context.index;
018
019import java.io.IOException;
020import java.net.URL;
021import java.util.ArrayList;
022import java.util.Enumeration;
023import java.util.List;
024import java.util.Properties;
025import java.util.concurrent.ConcurrentMap;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.core.SpringProperties;
031import org.springframework.core.io.UrlResource;
032import org.springframework.core.io.support.PropertiesLoaderUtils;
033import org.springframework.lang.Nullable;
034import org.springframework.util.ConcurrentReferenceHashMap;
035
036/**
037 * Candidate components index loading mechanism for internal use within the framework.
038 *
039 * @author Stephane Nicoll
040 * @since 5.0
041 */
042public final class CandidateComponentsIndexLoader {
043
044        /**
045         * The location to look for components.
046         * <p>Can be present in multiple JAR files.
047         */
048        public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components";
049
050        /**
051         * System property that instructs Spring to ignore the index, i.e.
052         * to always return {@code null} from {@link #loadIndex(ClassLoader)}.
053         * <p>The default is "false", allowing for regular use of the index. Switching this
054         * flag to {@code true} fulfills a corner case scenario when an index is partially
055         * available for some libraries (or use cases) but couldn't be built for the whole
056         * application. In this case, the application context fallbacks to a regular
057         * classpath arrangement (i.e. as no index was present at all).
058         */
059        public static final String IGNORE_INDEX = "spring.index.ignore";
060
061
062        private static final boolean shouldIgnoreIndex = SpringProperties.getFlag(IGNORE_INDEX);
063
064        private static final Log logger = LogFactory.getLog(CandidateComponentsIndexLoader.class);
065
066        private static final ConcurrentMap<ClassLoader, CandidateComponentsIndex> cache =
067                        new ConcurrentReferenceHashMap<>();
068
069
070        private CandidateComponentsIndexLoader() {
071        }
072
073
074        /**
075         * Load and instantiate the {@link CandidateComponentsIndex} from
076         * {@value #COMPONENTS_RESOURCE_LOCATION}, using the given class loader. If no
077         * index is available, return {@code null}.
078         * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
079         * @return the index to use or {@code null} if no index was found
080         * @throws IllegalArgumentException if any module index cannot
081         * be loaded or if an error occurs while creating {@link CandidateComponentsIndex}
082         */
083        @Nullable
084        public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) {
085                ClassLoader classLoaderToUse = classLoader;
086                if (classLoaderToUse == null) {
087                        classLoaderToUse = CandidateComponentsIndexLoader.class.getClassLoader();
088                }
089                return cache.computeIfAbsent(classLoaderToUse, CandidateComponentsIndexLoader::doLoadIndex);
090        }
091
092        @Nullable
093        private static CandidateComponentsIndex doLoadIndex(ClassLoader classLoader) {
094                if (shouldIgnoreIndex) {
095                        return null;
096                }
097
098                try {
099                        Enumeration<URL> urls = classLoader.getResources(COMPONENTS_RESOURCE_LOCATION);
100                        if (!urls.hasMoreElements()) {
101                                return null;
102                        }
103                        List<Properties> result = new ArrayList<>();
104                        while (urls.hasMoreElements()) {
105                                URL url = urls.nextElement();
106                                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
107                                result.add(properties);
108                        }
109                        if (logger.isDebugEnabled()) {
110                                logger.debug("Loaded " + result.size() + "] index(es)");
111                        }
112                        int totalCount = result.stream().mapToInt(Properties::size).sum();
113                        return (totalCount > 0 ? new CandidateComponentsIndex(result) : null);
114                }
115                catch (IOException ex) {
116                        throw new IllegalStateException("Unable to load indexes from location [" +
117                                        COMPONENTS_RESOURCE_LOCATION + "]", ex);
118                }
119        }
120
121}