001/* 002 * Copyright 2012-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 * http://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.boot.autoconfigure.context; 018 019import java.time.Duration; 020 021import org.springframework.boot.autoconfigure.AutoConfigureOrder; 022import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 023import org.springframework.boot.autoconfigure.condition.ConditionMessage; 024import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 025import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 026import org.springframework.boot.autoconfigure.condition.SearchStrategy; 027import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 028import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition; 029import org.springframework.boot.context.properties.ConfigurationProperties; 030import org.springframework.boot.context.properties.EnableConfigurationProperties; 031import org.springframework.context.MessageSource; 032import org.springframework.context.annotation.Bean; 033import org.springframework.context.annotation.ConditionContext; 034import org.springframework.context.annotation.Conditional; 035import org.springframework.context.annotation.Configuration; 036import org.springframework.context.support.ResourceBundleMessageSource; 037import org.springframework.core.Ordered; 038import org.springframework.core.io.Resource; 039import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 040import org.springframework.core.type.AnnotatedTypeMetadata; 041import org.springframework.util.ConcurrentReferenceHashMap; 042import org.springframework.util.StringUtils; 043 044/** 045 * {@link EnableAutoConfiguration Auto-configuration} for {@link MessageSource}. 046 * 047 * @author Dave Syer 048 * @author Phillip Webb 049 * @author EddĂș MelĂ©ndez 050 */ 051@Configuration 052@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT) 053@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 054@Conditional(ResourceBundleCondition.class) 055@EnableConfigurationProperties 056public class MessageSourceAutoConfiguration { 057 058 private static final Resource[] NO_RESOURCES = {}; 059 060 @Bean 061 @ConfigurationProperties(prefix = "spring.messages") 062 public MessageSourceProperties messageSourceProperties() { 063 return new MessageSourceProperties(); 064 } 065 066 @Bean 067 public MessageSource messageSource(MessageSourceProperties properties) { 068 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 069 if (StringUtils.hasText(properties.getBasename())) { 070 messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( 071 StringUtils.trimAllWhitespace(properties.getBasename()))); 072 } 073 if (properties.getEncoding() != null) { 074 messageSource.setDefaultEncoding(properties.getEncoding().name()); 075 } 076 messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); 077 Duration cacheDuration = properties.getCacheDuration(); 078 if (cacheDuration != null) { 079 messageSource.setCacheMillis(cacheDuration.toMillis()); 080 } 081 messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); 082 messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); 083 return messageSource; 084 } 085 086 protected static class ResourceBundleCondition extends SpringBootCondition { 087 088 private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>(); 089 090 @Override 091 public ConditionOutcome getMatchOutcome(ConditionContext context, 092 AnnotatedTypeMetadata metadata) { 093 String basename = context.getEnvironment() 094 .getProperty("spring.messages.basename", "messages"); 095 ConditionOutcome outcome = cache.get(basename); 096 if (outcome == null) { 097 outcome = getMatchOutcomeForBasename(context, basename); 098 cache.put(basename, outcome); 099 } 100 return outcome; 101 } 102 103 private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, 104 String basename) { 105 ConditionMessage.Builder message = ConditionMessage 106 .forCondition("ResourceBundle"); 107 for (String name : StringUtils.commaDelimitedListToStringArray( 108 StringUtils.trimAllWhitespace(basename))) { 109 for (Resource resource : getResources(context.getClassLoader(), name)) { 110 if (resource.exists()) { 111 return ConditionOutcome 112 .match(message.found("bundle").items(resource)); 113 } 114 } 115 } 116 return ConditionOutcome.noMatch( 117 message.didNotFind("bundle with basename " + basename).atAll()); 118 } 119 120 private Resource[] getResources(ClassLoader classLoader, String name) { 121 String target = name.replace('.', '/'); 122 try { 123 return new PathMatchingResourcePatternResolver(classLoader) 124 .getResources("classpath*:" + target + ".properties"); 125 } 126 catch (Exception ex) { 127 return NO_RESOURCES; 128 } 129 } 130 131 } 132 133}