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.autoconfigure;
018
019import java.io.File;
020import java.net.URL;
021import java.util.List;
022
023import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
024import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
025import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
026import org.springframework.boot.context.properties.EnableConfigurationProperties;
027import org.springframework.boot.devtools.autoconfigure.DevToolsProperties.Restart;
028import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
029import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
030import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
031import org.springframework.boot.devtools.classpath.PatternClassPathRestartStrategy;
032import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
033import org.springframework.boot.devtools.filewatch.FileSystemWatcherFactory;
034import org.springframework.boot.devtools.livereload.LiveReloadServer;
035import org.springframework.boot.devtools.restart.ConditionalOnInitializedRestarter;
036import org.springframework.boot.devtools.restart.RestartScope;
037import org.springframework.boot.devtools.restart.Restarter;
038import org.springframework.context.ApplicationEvent;
039import org.springframework.context.ApplicationListener;
040import org.springframework.context.annotation.Bean;
041import org.springframework.context.annotation.Configuration;
042import org.springframework.context.event.ContextRefreshedEvent;
043import org.springframework.context.event.GenericApplicationListener;
044import org.springframework.core.ResolvableType;
045import org.springframework.lang.Nullable;
046import org.springframework.util.StringUtils;
047
048/**
049 * {@link EnableAutoConfiguration Auto-configuration} for local development support.
050 *
051 * @author Phillip Webb
052 * @author Andy Wilkinson
053 * @author Vladimir Tsanev
054 * @since 1.3.0
055 */
056@Configuration
057@ConditionalOnInitializedRestarter
058@EnableConfigurationProperties(DevToolsProperties.class)
059public class LocalDevToolsAutoConfiguration {
060
061        /**
062         * Local LiveReload configuration.
063         */
064        @Configuration
065        @ConditionalOnProperty(prefix = "spring.devtools.livereload", name = "enabled", matchIfMissing = true)
066        static class LiveReloadConfiguration {
067
068                private final DevToolsProperties properties;
069
070                LiveReloadConfiguration(DevToolsProperties properties) {
071                        this.properties = properties;
072                }
073
074                @Bean
075                @RestartScope
076                @ConditionalOnMissingBean
077                public LiveReloadServer liveReloadServer() {
078                        return new LiveReloadServer(this.properties.getLivereload().getPort(),
079                                        Restarter.getInstance().getThreadFactory());
080                }
081
082                @Bean
083                public OptionalLiveReloadServer optionalLiveReloadServer(
084                                LiveReloadServer liveReloadServer) {
085                        return new OptionalLiveReloadServer(liveReloadServer);
086                }
087
088                @Bean
089                public LiveReloadServerEventListener liveReloadServerEventListener(
090                                OptionalLiveReloadServer liveReloadServer) {
091                        return new LiveReloadServerEventListener(liveReloadServer);
092                }
093
094        }
095
096        /**
097         * Local Restart Configuration.
098         */
099        @Configuration
100        @ConditionalOnProperty(prefix = "spring.devtools.restart", name = "enabled", matchIfMissing = true)
101        static class RestartConfiguration
102                        implements ApplicationListener<ClassPathChangedEvent> {
103
104                private final DevToolsProperties properties;
105
106                RestartConfiguration(DevToolsProperties properties) {
107                        this.properties = properties;
108                }
109
110                @Override
111                public void onApplicationEvent(ClassPathChangedEvent event) {
112                        if (event.isRestartRequired()) {
113                                Restarter.getInstance().restart(
114                                                new FileWatchingFailureHandler(fileSystemWatcherFactory()));
115                        }
116                }
117
118                @Bean
119                @ConditionalOnMissingBean
120                public ClassPathFileSystemWatcher classPathFileSystemWatcher() {
121                        URL[] urls = Restarter.getInstance().getInitialUrls();
122                        ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher(
123                                        fileSystemWatcherFactory(), classPathRestartStrategy(), urls);
124                        watcher.setStopWatcherOnRestart(true);
125                        return watcher;
126                }
127
128                @Bean
129                @ConditionalOnMissingBean
130                public ClassPathRestartStrategy classPathRestartStrategy() {
131                        return new PatternClassPathRestartStrategy(
132                                        this.properties.getRestart().getAllExclude());
133                }
134
135                @Bean
136                public HateoasObjenesisCacheDisabler hateoasObjenesisCacheDisabler() {
137                        return new HateoasObjenesisCacheDisabler();
138                }
139
140                @Bean
141                public FileSystemWatcherFactory fileSystemWatcherFactory() {
142                        return this::newFileSystemWatcher;
143                }
144
145                @Bean
146                @ConditionalOnProperty(prefix = "spring.devtools.restart", name = "log-condition-evaluation-delta", matchIfMissing = true)
147                public ConditionEvaluationDeltaLoggingListener conditionEvaluationDeltaLoggingListener() {
148                        return new ConditionEvaluationDeltaLoggingListener();
149                }
150
151                private FileSystemWatcher newFileSystemWatcher() {
152                        Restart restartProperties = this.properties.getRestart();
153                        FileSystemWatcher watcher = new FileSystemWatcher(true,
154                                        restartProperties.getPollInterval(),
155                                        restartProperties.getQuietPeriod());
156                        String triggerFile = restartProperties.getTriggerFile();
157                        if (StringUtils.hasLength(triggerFile)) {
158                                watcher.setTriggerFilter(new TriggerFileFilter(triggerFile));
159                        }
160                        List<File> additionalPaths = restartProperties.getAdditionalPaths();
161                        for (File path : additionalPaths) {
162                                watcher.addSourceFolder(path.getAbsoluteFile());
163                        }
164                        return watcher;
165                }
166
167        }
168
169        static class LiveReloadServerEventListener implements GenericApplicationListener {
170
171                private final OptionalLiveReloadServer liveReloadServer;
172
173                LiveReloadServerEventListener(OptionalLiveReloadServer liveReloadServer) {
174                        this.liveReloadServer = liveReloadServer;
175                }
176
177                @Override
178                public boolean supportsEventType(ResolvableType eventType) {
179                        Class<?> type = eventType.getRawClass();
180                        if (type == null) {
181                                return false;
182                        }
183                        return ContextRefreshedEvent.class.isAssignableFrom(type)
184                                        || ClassPathChangedEvent.class.isAssignableFrom(type);
185                }
186
187                @Override
188                public boolean supportsSourceType(@Nullable Class<?> sourceType) {
189                        return true;
190                }
191
192                @Override
193                public void onApplicationEvent(ApplicationEvent event) {
194                        if (event instanceof ContextRefreshedEvent
195                                        || (event instanceof ClassPathChangedEvent
196                                                        && !((ClassPathChangedEvent) event).isRestartRequired())) {
197                                this.liveReloadServer.triggerReload();
198                        }
199                }
200
201                @Override
202                public int getOrder() {
203                        return 0;
204                }
205
206        }
207
208}