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}