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}