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}