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.devtools.env;
018
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.commons.logging.Log;
024
025import org.springframework.boot.SpringApplication;
026import org.springframework.boot.devtools.logger.DevToolsLogFactory;
027import org.springframework.boot.devtools.restart.Restarter;
028import org.springframework.boot.env.EnvironmentPostProcessor;
029import org.springframework.core.Ordered;
030import org.springframework.core.annotation.Order;
031import org.springframework.core.env.ConfigurableEnvironment;
032import org.springframework.core.env.Environment;
033import org.springframework.core.env.MapPropertySource;
034import org.springframework.util.ClassUtils;
035
036/**
037 * {@link EnvironmentPostProcessor} to add properties that make sense when working at
038 * development time.
039 *
040 * @author Phillip Webb
041 * @author Andy Wilkinson
042 * @author Madhura Bhave
043 * @since 1.3.0
044 */
045@Order(Ordered.LOWEST_PRECEDENCE)
046public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostProcessor {
047
048        private static final Log logger = DevToolsLogFactory
049                        .getLog(DevToolsPropertyDefaultsPostProcessor.class);
050
051        private static final String ENABLED = "spring.devtools.add-properties";
052
053        private static final String WEB_LOGGING = "logging.level.web";
054
055        private static final String[] WEB_ENVIRONMENT_CLASSES = {
056                        "org.springframework.web.context.ConfigurableWebEnvironment",
057                        "org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment" };
058
059        private static final Map<String, Object> PROPERTIES;
060
061        static {
062                Map<String, Object> properties = new HashMap<>();
063                properties.put("spring.thymeleaf.cache", "false");
064                properties.put("spring.freemarker.cache", "false");
065                properties.put("spring.groovy.template.cache", "false");
066                properties.put("spring.mustache.cache", "false");
067                properties.put("server.servlet.session.persistent", "true");
068                properties.put("spring.h2.console.enabled", "true");
069                properties.put("spring.resources.cache.period", "0");
070                properties.put("spring.resources.chain.cache", "false");
071                properties.put("spring.template.provider.cache", "false");
072                properties.put("spring.mvc.log-resolved-exception", "true");
073                properties.put("server.error.include-stacktrace", "ALWAYS");
074                properties.put("server.servlet.jsp.init-parameters.development", "true");
075                properties.put("spring.reactor.stacktrace-mode.enabled", "true");
076                PROPERTIES = Collections.unmodifiableMap(properties);
077        }
078
079        @Override
080        public void postProcessEnvironment(ConfigurableEnvironment environment,
081                        SpringApplication application) {
082                if (isLocalApplication(environment)) {
083                        if (canAddProperties(environment)) {
084                                logger.info("Devtools property defaults active! Set '" + ENABLED
085                                                + "' to 'false' to disable");
086                                environment.getPropertySources()
087                                                .addLast(new MapPropertySource("devtools", PROPERTIES));
088                        }
089                        if (isWebApplication(environment)
090                                        && !environment.containsProperty(WEB_LOGGING)) {
091                                logger.info("For additional web related logging consider "
092                                                + "setting the '" + WEB_LOGGING + "' property to 'DEBUG'");
093                        }
094                }
095        }
096
097        private boolean isLocalApplication(ConfigurableEnvironment environment) {
098                return environment.getPropertySources().get("remoteUrl") == null;
099        }
100
101        private boolean canAddProperties(Environment environment) {
102                if (environment.getProperty(ENABLED, Boolean.class, true)) {
103                        return isRestarterInitialized() || isRemoteRestartEnabled(environment);
104                }
105                return false;
106        }
107
108        private boolean isRestarterInitialized() {
109                try {
110                        Restarter restarter = Restarter.getInstance();
111                        return (restarter != null && restarter.getInitialUrls() != null);
112                }
113                catch (Exception ex) {
114                        return false;
115                }
116        }
117
118        private boolean isRemoteRestartEnabled(Environment environment) {
119                return environment.containsProperty("spring.devtools.remote.secret");
120        }
121
122        private boolean isWebApplication(Environment environment) {
123                for (String candidate : WEB_ENVIRONMENT_CLASSES) {
124                        Class<?> environmentClass = resolveClassName(candidate,
125                                        environment.getClass().getClassLoader());
126                        if (environmentClass != null && environmentClass.isInstance(environment)) {
127                                return true;
128                        }
129                }
130                return false;
131        }
132
133        private Class<?> resolveClassName(String candidate, ClassLoader classLoader) {
134                try {
135                        return ClassUtils.resolveClassName(candidate, classLoader);
136                }
137                catch (IllegalArgumentException ex) {
138                        return null;
139                }
140        }
141
142}