001/* 002 * Copyright 2012-2017 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.nio.charset.Charset; 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 056@ConfigurationProperties(prefix = "spring.messages") 057public class MessageSourceAutoConfiguration { 058 059 private static final Resource[] NO_RESOURCES = {}; 060 061 /** 062 * Comma-separated list of basenames, each following the ResourceBundle convention. 063 * Essentially a fully-qualified classpath location. If it doesn't contain a package 064 * qualifier (such as "org.mypackage"), it will be resolved from the classpath root. 065 */ 066 private String basename = "messages"; 067 068 /** 069 * Message bundles encoding. 070 */ 071 private Charset encoding = Charset.forName("UTF-8"); 072 073 /** 074 * Loaded resource bundle files cache expiration, in seconds. When set to -1, bundles 075 * are cached forever. 076 */ 077 private int cacheSeconds = -1; 078 079 /** 080 * Set whether to fall back to the system Locale if no files for a specific Locale 081 * have been found. if this is turned off, the only fallback will be the default file 082 * (e.g. "messages.properties" for basename "messages"). 083 */ 084 private boolean fallbackToSystemLocale = true; 085 086 /** 087 * Set whether to always apply the MessageFormat rules, parsing even messages without 088 * arguments. 089 */ 090 private boolean alwaysUseMessageFormat = false; 091 092 @Bean 093 public MessageSource messageSource() { 094 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 095 if (StringUtils.hasText(this.basename)) { 096 messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( 097 StringUtils.trimAllWhitespace(this.basename))); 098 } 099 if (this.encoding != null) { 100 messageSource.setDefaultEncoding(this.encoding.name()); 101 } 102 messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); 103 messageSource.setCacheSeconds(this.cacheSeconds); 104 messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat); 105 return messageSource; 106 } 107 108 public String getBasename() { 109 return this.basename; 110 } 111 112 public void setBasename(String basename) { 113 this.basename = basename; 114 } 115 116 public Charset getEncoding() { 117 return this.encoding; 118 } 119 120 public void setEncoding(Charset encoding) { 121 this.encoding = encoding; 122 } 123 124 public int getCacheSeconds() { 125 return this.cacheSeconds; 126 } 127 128 public void setCacheSeconds(int cacheSeconds) { 129 this.cacheSeconds = cacheSeconds; 130 } 131 132 public boolean isFallbackToSystemLocale() { 133 return this.fallbackToSystemLocale; 134 } 135 136 public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { 137 this.fallbackToSystemLocale = fallbackToSystemLocale; 138 } 139 140 public boolean isAlwaysUseMessageFormat() { 141 return this.alwaysUseMessageFormat; 142 } 143 144 public void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat) { 145 this.alwaysUseMessageFormat = alwaysUseMessageFormat; 146 } 147 148 protected static class ResourceBundleCondition extends SpringBootCondition { 149 150 private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<String, ConditionOutcome>(); 151 152 @Override 153 public ConditionOutcome getMatchOutcome(ConditionContext context, 154 AnnotatedTypeMetadata metadata) { 155 String basename = context.getEnvironment() 156 .getProperty("spring.messages.basename", "messages"); 157 ConditionOutcome outcome = cache.get(basename); 158 if (outcome == null) { 159 outcome = getMatchOutcomeForBasename(context, basename); 160 cache.put(basename, outcome); 161 } 162 return outcome; 163 } 164 165 private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, 166 String basename) { 167 ConditionMessage.Builder message = ConditionMessage 168 .forCondition("ResourceBundle"); 169 for (String name : StringUtils.commaDelimitedListToStringArray( 170 StringUtils.trimAllWhitespace(basename))) { 171 for (Resource resource : getResources(context.getClassLoader(), name)) { 172 if (resource.exists()) { 173 return ConditionOutcome 174 .match(message.found("bundle").items(resource)); 175 } 176 } 177 } 178 return ConditionOutcome.noMatch( 179 message.didNotFind("bundle with basename " + basename).atAll()); 180 } 181 182 private Resource[] getResources(ClassLoader classLoader, String name) { 183 try { 184 return new PathMatchingResourcePatternResolver(classLoader) 185 .getResources("classpath*:" + name + ".properties"); 186 } 187 catch (Exception ex) { 188 return NO_RESOURCES; 189 } 190 } 191 192 } 193 194}