Add support for Restarting applications
Add Restarter class that can be used to restart a running application when underlying class files change. The Restarter is automatically initialized via a ApplicationListener and automatically detects classpath URLs that are likely to change (when not running from a fat jar). See gh-3084
This commit is contained in:
		
							parent
							
								
									da51785706
								
							
						
					
					
						commit
						a5c56ca482
					
				| 
						 | 
					@ -17,6 +17,7 @@
 | 
				
			||||||
package org.springframework.boot.developertools.autoconfigure;
 | 
					package org.springframework.boot.developertools.autoconfigure;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 | 
					import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 | 
				
			||||||
 | 
					import org.springframework.boot.developertools.restart.ConditionalOnInitializedRestarter;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.context.annotation.Configuration;
 | 
					import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +28,7 @@ import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
 * @since 1.3.0
 | 
					 * @since 1.3.0
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Configuration
 | 
					@Configuration
 | 
				
			||||||
 | 
					@ConditionalOnInitializedRestarter
 | 
				
			||||||
public class LocalDeveloperToolsAutoConfiguration {
 | 
					public class LocalDeveloperToolsAutoConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Bean
 | 
						@Bean
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,111 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					import java.net.URLClassLoader;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.Iterator;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A filtered collections of URLs which can be change after the application has started.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class ChangeableUrls implements Iterable<URL> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final String[] SKIPPED_PROJECTS = { "spring-boot",
 | 
				
			||||||
 | 
								"spring-boot-developer-tools", "spring-boot-autoconfigure",
 | 
				
			||||||
 | 
								"spring-boot-actuator", "spring-boot-starter" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Pattern STARTER_PATTERN = Pattern
 | 
				
			||||||
 | 
								.compile("\\/spring-boot-starter-[\\w-]+\\/");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final List<URL> urls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private ChangeableUrls(URL... urls) {
 | 
				
			||||||
 | 
							List<URL> reloadableUrls = new ArrayList<URL>(urls.length);
 | 
				
			||||||
 | 
							for (URL url : urls) {
 | 
				
			||||||
 | 
								if (isReloadable(url)) {
 | 
				
			||||||
 | 
									reloadableUrls.add(url);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							this.urls = Collections.unmodifiableList(reloadableUrls);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private boolean isReloadable(URL url) {
 | 
				
			||||||
 | 
							String urlString = url.toString();
 | 
				
			||||||
 | 
							return isFolderUrl(urlString) && !isSkipped(urlString);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private boolean isFolderUrl(String urlString) {
 | 
				
			||||||
 | 
							return urlString.startsWith("file:") && urlString.endsWith("/");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private boolean isSkipped(String urlString) {
 | 
				
			||||||
 | 
							// Skip certain spring-boot projects to allow them to be imported in the same IDE
 | 
				
			||||||
 | 
							for (String skipped : SKIPPED_PROJECTS) {
 | 
				
			||||||
 | 
								if (urlString.contains("/" + skipped + "/target/classes/")) {
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Skip all starter projects
 | 
				
			||||||
 | 
							if (STARTER_PATTERN.matcher(urlString).find()) {
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public Iterator<URL> iterator() {
 | 
				
			||||||
 | 
							return this.urls.iterator();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int size() {
 | 
				
			||||||
 | 
							return this.urls.size();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public URL[] toArray() {
 | 
				
			||||||
 | 
							return this.urls.toArray(new URL[this.urls.size()]);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public List<URL> toList() {
 | 
				
			||||||
 | 
							return Collections.unmodifiableList(this.urls);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return this.urls.toString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static ChangeableUrls fromUrlClassLoader(URLClassLoader classLoader) {
 | 
				
			||||||
 | 
							return fromUrls(classLoader.getURLs());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static ChangeableUrls fromUrls(Collection<URL> urls) {
 | 
				
			||||||
 | 
							return fromUrls(new ArrayList<URL>(urls).toArray(new URL[urls.size()]));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static ChangeableUrls fromUrls(URL... urls) {
 | 
				
			||||||
 | 
							return new ChangeableUrls(urls);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.annotation.Documented;
 | 
				
			||||||
 | 
					import java.lang.annotation.ElementType;
 | 
				
			||||||
 | 
					import java.lang.annotation.Retention;
 | 
				
			||||||
 | 
					import java.lang.annotation.RetentionPolicy;
 | 
				
			||||||
 | 
					import java.lang.annotation.Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Conditional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link Conditional} that only matches when the {@link RestartInitializer} has been
 | 
				
			||||||
 | 
					 * applied with non {@code null} URLs.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 * @since 1.3.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Target({ ElementType.TYPE, ElementType.METHOD })
 | 
				
			||||||
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
 | 
					@Documented
 | 
				
			||||||
 | 
					@Conditional(OnInitializedRestarterCondition.class)
 | 
				
			||||||
 | 
					public @interface ConditionalOnInitializedRestarter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,94 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					import java.net.URLClassLoader;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.LinkedHashSet;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Default {@link RestartInitializer} that only enable initial restart when running a
 | 
				
			||||||
 | 
					 * standard "main" method. Skips initialization when running "fat" jars (included
 | 
				
			||||||
 | 
					 * exploded) or when running from a test.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 * @since 1.3.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class DefaultRestartInitializer implements RestartInitializer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Set<String> SKIPPED_STACK_ELEMENTS;
 | 
				
			||||||
 | 
						static {
 | 
				
			||||||
 | 
							Set<String> skipped = new LinkedHashSet<String>();
 | 
				
			||||||
 | 
							skipped.add("org.junit.runners.");
 | 
				
			||||||
 | 
							skipped.add("org.springframework.boot.test.");
 | 
				
			||||||
 | 
							SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public URL[] getInitialUrls(Thread thread) {
 | 
				
			||||||
 | 
							if (!isMain(thread)) {
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for (StackTraceElement element : thread.getStackTrace()) {
 | 
				
			||||||
 | 
								if (isSkippedStackElement(element)) {
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return getUrls(thread);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Returns if the thread is for a main invocation. By default checks the name of the
 | 
				
			||||||
 | 
						 * thread and the context classloader.
 | 
				
			||||||
 | 
						 * @param thread the thread to check
 | 
				
			||||||
 | 
						 * @return {@code true} if the thread is a main invocation
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected boolean isMain(Thread thread) {
 | 
				
			||||||
 | 
							return thread.getName().equals("main")
 | 
				
			||||||
 | 
									&& thread.getContextClassLoader().getClass().getName()
 | 
				
			||||||
 | 
											.contains("AppClassLoader");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Checks if a specific {@link StackTraceElement} should cause the initializer to be
 | 
				
			||||||
 | 
						 * skipped.
 | 
				
			||||||
 | 
						 * @param element the stack element to check
 | 
				
			||||||
 | 
						 * @return {@code true} if the stack element means that the initializer should be
 | 
				
			||||||
 | 
						 * skipped
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected boolean isSkippedStackElement(StackTraceElement element) {
 | 
				
			||||||
 | 
							for (String skipped : SKIPPED_STACK_ELEMENTS) {
 | 
				
			||||||
 | 
								if (element.getClassName().startsWith(skipped)) {
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Return the URLs that should be used with initialization.
 | 
				
			||||||
 | 
						 * @param thread the source thread
 | 
				
			||||||
 | 
						 * @return the URLs
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected URL[] getUrls(Thread thread) {
 | 
				
			||||||
 | 
							return ChangeableUrls.fromUrlClassLoader(
 | 
				
			||||||
 | 
									(URLClassLoader) thread.getContextClassLoader()).toArray();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,84 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.reflect.Method;
 | 
				
			||||||
 | 
					import java.lang.reflect.Modifier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.util.Assert;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The "main" method located from a running thread.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class MainMethod {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Method method;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public MainMethod() {
 | 
				
			||||||
 | 
							this(Thread.currentThread());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public MainMethod(Thread thread) {
 | 
				
			||||||
 | 
							Assert.notNull(thread, "Thread must not be null");
 | 
				
			||||||
 | 
							this.method = getMainMethod(thread);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Method getMainMethod(Thread thread) {
 | 
				
			||||||
 | 
							for (StackTraceElement element : thread.getStackTrace()) {
 | 
				
			||||||
 | 
								if ("main".equals(element.getMethodName())) {
 | 
				
			||||||
 | 
									Method method = getMainMethod(element);
 | 
				
			||||||
 | 
									if (method != null) {
 | 
				
			||||||
 | 
										return method;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							throw new IllegalStateException("Unable to find main method");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Method getMainMethod(StackTraceElement element) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								Class<?> elementClass = Class.forName(element.getClassName());
 | 
				
			||||||
 | 
								Method method = elementClass.getDeclaredMethod("main", String[].class);
 | 
				
			||||||
 | 
								if (Modifier.isStatic(method.getModifiers())) {
 | 
				
			||||||
 | 
									return method;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (Exception ex) {
 | 
				
			||||||
 | 
								// Ignore
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Returns the actual main method.
 | 
				
			||||||
 | 
						 * @return the main method
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Method getMethod() {
 | 
				
			||||||
 | 
							return this.method;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Return the name of the declaring class.
 | 
				
			||||||
 | 
						 * @return the declaring class name
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public String getDeclaringClassName() {
 | 
				
			||||||
 | 
							return this.method.getDeclaringClass().getName();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
 | 
				
			||||||
 | 
					import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Condition;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.ConditionContext;
 | 
				
			||||||
 | 
					import org.springframework.core.type.AnnotatedTypeMetadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link Condition} that checks that a {@link Restarter} is available an initialized.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 * @see ConditionalOnInitializedRestarter
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class OnInitializedRestarterCondition extends SpringBootCondition {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public ConditionOutcome getMatchOutcome(ConditionContext context,
 | 
				
			||||||
 | 
								AnnotatedTypeMetadata metadata) {
 | 
				
			||||||
 | 
							Restarter restarter = getRestarter();
 | 
				
			||||||
 | 
							if (restarter == null) {
 | 
				
			||||||
 | 
								return ConditionOutcome.noMatch("Restarter unavailable");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (restarter.getInitialUrls() == null) {
 | 
				
			||||||
 | 
								return ConditionOutcome.noMatch("Restarter initialized without URLs");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return ConditionOutcome.match("Restarter available and initialized");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Restarter getRestarter() {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								return Restarter.getInstance();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (Exception ex) {
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.boot.context.event.ApplicationFailedEvent;
 | 
				
			||||||
 | 
					import org.springframework.boot.context.event.ApplicationReadyEvent;
 | 
				
			||||||
 | 
					import org.springframework.boot.context.event.ApplicationStartedEvent;
 | 
				
			||||||
 | 
					import org.springframework.context.ApplicationEvent;
 | 
				
			||||||
 | 
					import org.springframework.context.ApplicationListener;
 | 
				
			||||||
 | 
					import org.springframework.core.Ordered;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link ApplicationListener} to initialize the {@link Restarter}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 * @since 1.3.0
 | 
				
			||||||
 | 
					 * @see Restarter
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class RestartApplicationListener implements ApplicationListener<ApplicationEvent>,
 | 
				
			||||||
 | 
							Ordered {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private int order = HIGHEST_PRECEDENCE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void onApplicationEvent(ApplicationEvent event) {
 | 
				
			||||||
 | 
							if (event instanceof ApplicationStartedEvent) {
 | 
				
			||||||
 | 
								Restarter.initialize(((ApplicationStartedEvent) event).getArgs());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (event instanceof ApplicationReadyEvent
 | 
				
			||||||
 | 
									|| event instanceof ApplicationFailedEvent) {
 | 
				
			||||||
 | 
								Restarter.getInstance().finish();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public int getOrder() {
 | 
				
			||||||
 | 
							return this.order;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Set the order of the listener.
 | 
				
			||||||
 | 
						 * @param order the order of the listener
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void setOrder(int order) {
 | 
				
			||||||
 | 
							this.order = order;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Strategy interface used to initialize a {@link Restarter}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 * @since 1.3.0
 | 
				
			||||||
 | 
					 * @see DefaultRestartInitializer
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public interface RestartInitializer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * {@link RestartInitializer} that doesn't return any URLs.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static final RestartInitializer NONE = new RestartInitializer() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public URL[] getInitialUrls(Thread thread) {
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Return the initial set of URLs for the {@link Restarter} or {@code null} if no
 | 
				
			||||||
 | 
						 * initial restart is required.
 | 
				
			||||||
 | 
						 * @param thread the source thread
 | 
				
			||||||
 | 
						 * @return initial URLs or {@code null}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						URL[] getInitialUrls(Thread thread);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.reflect.Method;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Thread used to launch a restarted application.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class RestartLauncher extends Thread {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final String mainClassName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final String[] args;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public RestartLauncher(ClassLoader classLoader, String mainClassName, String[] args,
 | 
				
			||||||
 | 
								UncaughtExceptionHandler exceptionHandler) {
 | 
				
			||||||
 | 
							this.mainClassName = mainClassName;
 | 
				
			||||||
 | 
							this.args = args;
 | 
				
			||||||
 | 
							setName("restartedMain");
 | 
				
			||||||
 | 
							setUncaughtExceptionHandler(exceptionHandler);
 | 
				
			||||||
 | 
							setDaemon(false);
 | 
				
			||||||
 | 
							setContextClassLoader(classLoader);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void run() {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								Class<?> mainClass = getContextClassLoader().loadClass(this.mainClassName);
 | 
				
			||||||
 | 
								Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
 | 
				
			||||||
 | 
								mainMethod.invoke(null, new Object[] { this.args });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (Exception ex) {
 | 
				
			||||||
 | 
								throw new IllegalStateException(ex);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,519 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.beans.Introspector;
 | 
				
			||||||
 | 
					import java.lang.Thread.UncaughtExceptionHandler;
 | 
				
			||||||
 | 
					import java.lang.reflect.Field;
 | 
				
			||||||
 | 
					import java.lang.reflect.Method;
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.IdentityHashMap;
 | 
				
			||||||
 | 
					import java.util.Iterator;
 | 
				
			||||||
 | 
					import java.util.LinkedList;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					import java.util.concurrent.BlockingDeque;
 | 
				
			||||||
 | 
					import java.util.concurrent.Callable;
 | 
				
			||||||
 | 
					import java.util.concurrent.LinkedBlockingDeque;
 | 
				
			||||||
 | 
					import java.util.concurrent.ThreadFactory;
 | 
				
			||||||
 | 
					import java.util.concurrent.locks.Lock;
 | 
				
			||||||
 | 
					import java.util.concurrent.locks.ReentrantLock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.apache.commons.logging.Log;
 | 
				
			||||||
 | 
					import org.apache.commons.logging.LogFactory;
 | 
				
			||||||
 | 
					import org.springframework.beans.CachedIntrospectionResults;
 | 
				
			||||||
 | 
					import org.springframework.boot.SpringApplication;
 | 
				
			||||||
 | 
					import org.springframework.boot.developertools.restart.classloader.RestartClassLoader;
 | 
				
			||||||
 | 
					import org.springframework.boot.logging.DeferredLog;
 | 
				
			||||||
 | 
					import org.springframework.cglib.core.ClassNameReader;
 | 
				
			||||||
 | 
					import org.springframework.core.ResolvableType;
 | 
				
			||||||
 | 
					import org.springframework.core.annotation.AnnotationUtils;
 | 
				
			||||||
 | 
					import org.springframework.util.Assert;
 | 
				
			||||||
 | 
					import org.springframework.util.ReflectionUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Allows a running application to be restarted with an updated classpath. The restarter
 | 
				
			||||||
 | 
					 * works by creating a new application ClassLoader that is split into two parts. The top
 | 
				
			||||||
 | 
					 * part contains static URLs that don't change (for example 3rd party libraries and Spring
 | 
				
			||||||
 | 
					 * Boot itself) and the bottom part contains URLs where classes and resources might be
 | 
				
			||||||
 | 
					 * updated.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * The Restarter should be {@link #initialize(String[]) initialized} early to ensure that
 | 
				
			||||||
 | 
					 * classes are loaded multiple times. Mostly the {@link RestartApplicationListener} can be
 | 
				
			||||||
 | 
					 * relied upon to perform initialization, however, you may need to call
 | 
				
			||||||
 | 
					 * {@link #initialize(String[])} directly if your SpringApplication arguments are not
 | 
				
			||||||
 | 
					 * identical to your main method arguments.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * By default, applications running in an IDE (i.e. those not packaged as "fat jars") will
 | 
				
			||||||
 | 
					 * automatically detect URLs that can change. It's also possible to manually configure
 | 
				
			||||||
 | 
					 * URLs or class file updates for remote restart scenarios.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 * @since 1.3.0
 | 
				
			||||||
 | 
					 * @see RestartApplicationListener
 | 
				
			||||||
 | 
					 * @see #initialize(String[])
 | 
				
			||||||
 | 
					 * @see #getInstance()
 | 
				
			||||||
 | 
					 * @see #restart()
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class Restarter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static Restarter instance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Log logger = new DeferredLog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final boolean forceReferenceCleanup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private URL[] urls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final String mainClassName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final ClassLoader applicationClassLoader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final String[] args;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final UncaughtExceptionHandler exceptionHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final BlockingDeque<LeakSafeThread> leakSafeThreads = new LinkedBlockingDeque<LeakSafeThread>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private boolean finished = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Lock stopLock = new ReentrantLock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Internal constructor to create a new {@link Restarter} instance.
 | 
				
			||||||
 | 
						 * @param thread the source thread
 | 
				
			||||||
 | 
						 * @param args the application arguments
 | 
				
			||||||
 | 
						 * @param forceReferenceCleanup if soft/weak reference cleanup should be forced
 | 
				
			||||||
 | 
						 * @param initializer
 | 
				
			||||||
 | 
						 * @see #initialize(String[])
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected Restarter(Thread thread, String[] args, boolean forceReferenceCleanup,
 | 
				
			||||||
 | 
								RestartInitializer initializer) {
 | 
				
			||||||
 | 
							Assert.notNull(thread, "Thread must not be null");
 | 
				
			||||||
 | 
							Assert.notNull(args, "Args must not be null");
 | 
				
			||||||
 | 
							Assert.notNull(initializer, "Initializer must not be null");
 | 
				
			||||||
 | 
							this.logger.debug("Creating new Restarter for thread " + thread);
 | 
				
			||||||
 | 
							SilentExitExceptionHandler.setup(thread);
 | 
				
			||||||
 | 
							this.forceReferenceCleanup = forceReferenceCleanup;
 | 
				
			||||||
 | 
							this.urls = initializer.getInitialUrls(thread);
 | 
				
			||||||
 | 
							this.mainClassName = getMainClassName(thread);
 | 
				
			||||||
 | 
							this.applicationClassLoader = thread.getContextClassLoader();
 | 
				
			||||||
 | 
							this.args = args;
 | 
				
			||||||
 | 
							this.exceptionHandler = thread.getUncaughtExceptionHandler();
 | 
				
			||||||
 | 
							this.leakSafeThreads.add(new LeakSafeThread());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private String getMainClassName(Thread thread) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								return new MainMethod(thread).getDeclaringClassName();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (Exception ex) {
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected void initialize(boolean restartOnInitialize) {
 | 
				
			||||||
 | 
							preInitializeLeakyClasses();
 | 
				
			||||||
 | 
							if (this.urls != null) {
 | 
				
			||||||
 | 
								if (restartOnInitialize) {
 | 
				
			||||||
 | 
									this.logger.debug("Immediately restarting application");
 | 
				
			||||||
 | 
									immediateRestart();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void immediateRestart() {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								getLeakSafeThread().callAndWait(new Callable<Void>() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									@Override
 | 
				
			||||||
 | 
									public Void call() throws Exception {
 | 
				
			||||||
 | 
										start();
 | 
				
			||||||
 | 
										return null;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (Exception ex) {
 | 
				
			||||||
 | 
								this.logger.warn("Unable to initialize restarter", ex);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							SilentExitExceptionHandler.exitCurrentThread();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * CGLIB has a private exception field which needs to initialized early to ensure that
 | 
				
			||||||
 | 
						 * the stacktrace doesn't retain a reference to the RestartClassLoader.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private void preInitializeLeakyClasses() {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								Class<?> readerClass = ClassNameReader.class;
 | 
				
			||||||
 | 
								Field field = readerClass.getDeclaredField("EARLY_EXIT");
 | 
				
			||||||
 | 
								field.setAccessible(true);
 | 
				
			||||||
 | 
								((Throwable) field.get(null)).fillInStackTrace();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (Exception ex) {
 | 
				
			||||||
 | 
								this.logger.warn("Unable to pre-initialize classes", ex);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Return a {@link ThreadFactory} that can be used to create leak safe threads.
 | 
				
			||||||
 | 
						 * @return a leak safe thread factory
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public ThreadFactory getThreadFactory() {
 | 
				
			||||||
 | 
							return new LeakSafeThreadFactory();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Restart the running application.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void restart() {
 | 
				
			||||||
 | 
							this.logger.debug("Restarting application");
 | 
				
			||||||
 | 
							getLeakSafeThread().call(new Callable<Void>() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public Void call() throws Exception {
 | 
				
			||||||
 | 
									Restarter.this.stop();
 | 
				
			||||||
 | 
									Restarter.this.start();
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Start the application.
 | 
				
			||||||
 | 
						 * @throws Exception
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected void start() throws Exception {
 | 
				
			||||||
 | 
							Assert.notNull(this.mainClassName, "Unable to find the main class to restart");
 | 
				
			||||||
 | 
							ClassLoader parent = this.applicationClassLoader;
 | 
				
			||||||
 | 
							ClassLoader classLoader = new RestartClassLoader(parent, this.urls, this.logger);
 | 
				
			||||||
 | 
							if (this.logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
								this.logger.debug("Starting application " + this.mainClassName
 | 
				
			||||||
 | 
										+ " with URLs " + Arrays.asList(this.urls));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							relaunch(classLoader);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Relaunch the application using the specified classloader.
 | 
				
			||||||
 | 
						 * @param classLoader the classloader to use
 | 
				
			||||||
 | 
						 * @throws Exception
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected void relaunch(ClassLoader classLoader) throws Exception {
 | 
				
			||||||
 | 
							RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName,
 | 
				
			||||||
 | 
									this.args, this.exceptionHandler);
 | 
				
			||||||
 | 
							launcher.start();
 | 
				
			||||||
 | 
							launcher.join();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Stop the application.
 | 
				
			||||||
 | 
						 * @throws Exception
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected void stop() throws Exception {
 | 
				
			||||||
 | 
							this.logger.debug("Stopping application");
 | 
				
			||||||
 | 
							this.stopLock.lock();
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								triggerShutdownHooks();
 | 
				
			||||||
 | 
								cleanupCaches();
 | 
				
			||||||
 | 
								if (this.forceReferenceCleanup) {
 | 
				
			||||||
 | 
									forceReferenceCleanup();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							finally {
 | 
				
			||||||
 | 
								this.stopLock.unlock();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							System.gc();
 | 
				
			||||||
 | 
							System.runFinalization();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@SuppressWarnings("rawtypes")
 | 
				
			||||||
 | 
						private void triggerShutdownHooks() throws Exception {
 | 
				
			||||||
 | 
							Class<?> hooksClass = Class.forName("java.lang.ApplicationShutdownHooks");
 | 
				
			||||||
 | 
							Method runHooks = hooksClass.getDeclaredMethod("runHooks");
 | 
				
			||||||
 | 
							runHooks.setAccessible(true);
 | 
				
			||||||
 | 
							runHooks.invoke(null);
 | 
				
			||||||
 | 
							Field field = hooksClass.getDeclaredField("hooks");
 | 
				
			||||||
 | 
							field.setAccessible(true);
 | 
				
			||||||
 | 
							field.set(null, new IdentityHashMap());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void cleanupCaches() throws Exception {
 | 
				
			||||||
 | 
							Introspector.flushCaches();
 | 
				
			||||||
 | 
							cleanupKnownCaches();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void cleanupKnownCaches() throws Exception {
 | 
				
			||||||
 | 
							// Whilst not strictly necessary it helps to cleanup soft reference caches
 | 
				
			||||||
 | 
							// early rather than waiting for memory limits to be reached
 | 
				
			||||||
 | 
							clear(ResolvableType.class, "cache");
 | 
				
			||||||
 | 
							clear("org.springframework.core.SerializableTypeWrapper", "cache");
 | 
				
			||||||
 | 
							clear(CachedIntrospectionResults.class, "acceptedClassLoaders");
 | 
				
			||||||
 | 
							clear(CachedIntrospectionResults.class, "strongClassCache");
 | 
				
			||||||
 | 
							clear(CachedIntrospectionResults.class, "softClassCache");
 | 
				
			||||||
 | 
							clear(ReflectionUtils.class, "declaredFieldsCache");
 | 
				
			||||||
 | 
							clear(ReflectionUtils.class, "declaredMethodsCache");
 | 
				
			||||||
 | 
							clear(AnnotationUtils.class, "findAnnotationCache");
 | 
				
			||||||
 | 
							clear(AnnotationUtils.class, "annotatedInterfaceCache");
 | 
				
			||||||
 | 
							clear("com.sun.naming.internal.ResourceManager", "propertiesCache");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void clear(String className, String fieldName) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								clear(Class.forName(className), fieldName);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (Exception ex) {
 | 
				
			||||||
 | 
								this.logger.debug("Unable to clear field " + className + " " + fieldName, ex);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void clear(Class<?> type, String fieldName) throws Exception {
 | 
				
			||||||
 | 
							Field field = type.getDeclaredField(fieldName);
 | 
				
			||||||
 | 
							field.setAccessible(true);
 | 
				
			||||||
 | 
							Object instance = field.get(null);
 | 
				
			||||||
 | 
							if (instance instanceof Set) {
 | 
				
			||||||
 | 
								((Set<?>) instance).clear();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (instance instanceof Map) {
 | 
				
			||||||
 | 
								Map<?, ?> map = ((Map<?, ?>) instance);
 | 
				
			||||||
 | 
								for (Iterator<?> iterator = map.keySet().iterator(); iterator.hasNext();) {
 | 
				
			||||||
 | 
									Object value = iterator.next();
 | 
				
			||||||
 | 
									if (value instanceof Class
 | 
				
			||||||
 | 
											&& ((Class<?>) value).getClassLoader() instanceof RestartClassLoader) {
 | 
				
			||||||
 | 
										iterator.remove();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Cleanup any soft/weak references by forcing an {@link OutOfMemoryError} error.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private void forceReferenceCleanup() {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								final List<long[]> memory = new LinkedList<long[]>();
 | 
				
			||||||
 | 
								while (true) {
 | 
				
			||||||
 | 
									memory.add(new long[102400]);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (final OutOfMemoryError ex) {
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Called to finish {@link Restarter} initialization when application logging is
 | 
				
			||||||
 | 
						 * available.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						synchronized void finish() {
 | 
				
			||||||
 | 
							if (!isFinished()) {
 | 
				
			||||||
 | 
								this.logger = DeferredLog.replay(this.logger, LogFactory.getLog(getClass()));
 | 
				
			||||||
 | 
								this.finished = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						boolean isFinished() {
 | 
				
			||||||
 | 
							return this.finished;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private LeakSafeThread getLeakSafeThread() {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								return this.leakSafeThreads.takeFirst();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							catch (InterruptedException ex) {
 | 
				
			||||||
 | 
								Thread.currentThread().interrupt();
 | 
				
			||||||
 | 
								throw new IllegalStateException(ex);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Return the initial set of URLs as configured by the {@link RestartInitializer}.
 | 
				
			||||||
 | 
						 * @return the initial URLs or {@code null}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public URL[] getInitialUrls() {
 | 
				
			||||||
 | 
							return this.urls;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initialize restart support. See
 | 
				
			||||||
 | 
						 * {@link #initialize(String[], boolean, RestartInitializer)} for details.
 | 
				
			||||||
 | 
						 * @param args main application arguments
 | 
				
			||||||
 | 
						 * @see #initialize(String[], boolean, RestartInitializer)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void initialize(String[] args) {
 | 
				
			||||||
 | 
							initialize(args, false, new DefaultRestartInitializer());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initialize restart support. See
 | 
				
			||||||
 | 
						 * {@link #initialize(String[], boolean, RestartInitializer)} for details.
 | 
				
			||||||
 | 
						 * @param args main application arguments
 | 
				
			||||||
 | 
						 * @param initializer the restart initializer
 | 
				
			||||||
 | 
						 * @see #initialize(String[], boolean, RestartInitializer)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void initialize(String[] args, RestartInitializer initializer) {
 | 
				
			||||||
 | 
							initialize(args, false, initializer, true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initialize restart support. See
 | 
				
			||||||
 | 
						 * {@link #initialize(String[], boolean, RestartInitializer)} for details.
 | 
				
			||||||
 | 
						 * @param args main application arguments
 | 
				
			||||||
 | 
						 * @param forceReferenceCleanup if forcing of soft/weak reference should happen on
 | 
				
			||||||
 | 
						 * @see #initialize(String[], boolean, RestartInitializer)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void initialize(String[] args, boolean forceReferenceCleanup) {
 | 
				
			||||||
 | 
							initialize(args, forceReferenceCleanup, new DefaultRestartInitializer());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initialize restart support. See
 | 
				
			||||||
 | 
						 * {@link #initialize(String[], boolean, RestartInitializer)} for details.
 | 
				
			||||||
 | 
						 * @param args main application arguments
 | 
				
			||||||
 | 
						 * @param forceReferenceCleanup if forcing of soft/weak reference should happen on
 | 
				
			||||||
 | 
						 * @param initializer the restart initializer
 | 
				
			||||||
 | 
						 * @see #initialize(String[], boolean, RestartInitializer)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void initialize(String[] args, boolean forceReferenceCleanup,
 | 
				
			||||||
 | 
								RestartInitializer initializer) {
 | 
				
			||||||
 | 
							initialize(args, forceReferenceCleanup, initializer, true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initialize restart support for the current application. Called automatically by
 | 
				
			||||||
 | 
						 * {@link RestartApplicationListener} but can also be called directly if main
 | 
				
			||||||
 | 
						 * application arguments are not the same as those passed to the
 | 
				
			||||||
 | 
						 * {@link SpringApplication}.
 | 
				
			||||||
 | 
						 * @param args main application arguments
 | 
				
			||||||
 | 
						 * @param forceReferenceCleanup if forcing of soft/weak reference should happen on
 | 
				
			||||||
 | 
						 * each restart. This will slow down restarts and is intended primarily for testing
 | 
				
			||||||
 | 
						 * @param initializer the restart initializer
 | 
				
			||||||
 | 
						 * @param restartOnInitialize if the restarter should be restarted immediately when
 | 
				
			||||||
 | 
						 * the {@link RestartInitializer} returns non {@code null} results
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void initialize(String[] args, boolean forceReferenceCleanup,
 | 
				
			||||||
 | 
								RestartInitializer initializer, boolean restartOnInitialize) {
 | 
				
			||||||
 | 
							if (instance == null) {
 | 
				
			||||||
 | 
								synchronized (Restarter.class) {
 | 
				
			||||||
 | 
									instance = new Restarter(Thread.currentThread(), args,
 | 
				
			||||||
 | 
											forceReferenceCleanup, initializer);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								instance.initialize(restartOnInitialize);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Return the active {@link Restarter} instance. Cannot be called before
 | 
				
			||||||
 | 
						 * {@link #initialize(String[]) initialization}.
 | 
				
			||||||
 | 
						 * @return the restarter
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public synchronized static Restarter getInstance() {
 | 
				
			||||||
 | 
							Assert.state(instance != null, "Restarter has not been initialized");
 | 
				
			||||||
 | 
							return instance;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Set the restarter instance (useful for testing).
 | 
				
			||||||
 | 
						 * @param instance the instance to set
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						final static void setInstance(Restarter instance) {
 | 
				
			||||||
 | 
							Restarter.instance = instance;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Clear the instance. Primarily provided for tests and not usually used in
 | 
				
			||||||
 | 
						 * application code.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void clearInstance() {
 | 
				
			||||||
 | 
							instance = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Thread that is created early so not to retain the {@link RestartClassLoader}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private class LeakSafeThread extends Thread {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private Callable<?> callable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private Object result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public LeakSafeThread() {
 | 
				
			||||||
 | 
								setDaemon(false);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public void call(Callable<?> callable) {
 | 
				
			||||||
 | 
								this.callable = callable;
 | 
				
			||||||
 | 
								start();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@SuppressWarnings("unchecked")
 | 
				
			||||||
 | 
							public <V> V callAndWait(Callable<V> callable) {
 | 
				
			||||||
 | 
								this.callable = callable;
 | 
				
			||||||
 | 
								start();
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									join();
 | 
				
			||||||
 | 
									return (V) this.result;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (InterruptedException ex) {
 | 
				
			||||||
 | 
									Thread.currentThread().interrupt();
 | 
				
			||||||
 | 
									throw new IllegalStateException(ex);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public void run() {
 | 
				
			||||||
 | 
								// We are safe to refresh the ActionThread (and indirectly call
 | 
				
			||||||
 | 
								// AccessController.getContext()) since our stack doesn't include the
 | 
				
			||||||
 | 
								// RestartClassLoader
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									Restarter.this.leakSafeThreads.put(new LeakSafeThread());
 | 
				
			||||||
 | 
									this.result = this.callable.call();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (Exception ex) {
 | 
				
			||||||
 | 
									ex.printStackTrace();
 | 
				
			||||||
 | 
									System.exit(1);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * {@link ThreadFactory} that creates a leak safe thead.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private class LeakSafeThreadFactory implements ThreadFactory {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public Thread newThread(final Runnable runnable) {
 | 
				
			||||||
 | 
								return getLeakSafeThread().callAndWait(new Callable<Thread>() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									@Override
 | 
				
			||||||
 | 
									public Thread call() throws Exception {
 | 
				
			||||||
 | 
										Thread thread = new Thread(runnable);
 | 
				
			||||||
 | 
										thread.setContextClassLoader(Restarter.this.applicationClassLoader);
 | 
				
			||||||
 | 
										return thread;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,60 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2014 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.Thread.UncaughtExceptionHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link UncaughtExceptionHandler} decorator that allows a thread to exit silently.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class SilentExitExceptionHandler implements UncaughtExceptionHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final UncaughtExceptionHandler delegate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public SilentExitExceptionHandler(UncaughtExceptionHandler delegate) {
 | 
				
			||||||
 | 
							this.delegate = delegate;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void uncaughtException(Thread thread, Throwable exception) {
 | 
				
			||||||
 | 
							if (exception instanceof SilentExitException) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (this.delegate != null) {
 | 
				
			||||||
 | 
								this.delegate.uncaughtException(thread, exception);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void setup(Thread thread) {
 | 
				
			||||||
 | 
							UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler();
 | 
				
			||||||
 | 
							if (!(handler instanceof SilentExitExceptionHandler)) {
 | 
				
			||||||
 | 
								handler = new SilentExitExceptionHandler(handler);
 | 
				
			||||||
 | 
								thread.setUncaughtExceptionHandler(handler);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void exitCurrentThread() {
 | 
				
			||||||
 | 
							throw new SilentExitException();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class SilentExitException extends RuntimeException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Application restart support
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,7 @@
 | 
				
			||||||
 | 
					# Application Listeners
 | 
				
			||||||
 | 
					org.springframework.context.ApplicationListener=\
 | 
				
			||||||
 | 
					org.springframework.boot.developertools.restart.RestartApplicationListener
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Auto Configure
 | 
					# Auto Configure
 | 
				
			||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 | 
					org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 | 
				
			||||||
org.springframework.boot.developertools.autoconfigure.LocalDeveloperToolsAutoConfiguration
 | 
					org.springframework.boot.developertools.autoconfigure.LocalDeveloperToolsAutoConfiguration
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,9 @@ import org.junit.Test;
 | 
				
			||||||
import org.junit.rules.ExpectedException;
 | 
					import org.junit.rules.ExpectedException;
 | 
				
			||||||
import org.springframework.boot.SpringApplication;
 | 
					import org.springframework.boot.SpringApplication;
 | 
				
			||||||
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
 | 
					import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
 | 
				
			||||||
 | 
					import org.springframework.boot.developertools.restart.MockRestartInitializer;
 | 
				
			||||||
 | 
					import org.springframework.boot.developertools.restart.MockRestarter;
 | 
				
			||||||
 | 
					import org.springframework.boot.developertools.restart.Restarter;
 | 
				
			||||||
import org.springframework.context.ConfigurableApplicationContext;
 | 
					import org.springframework.context.ConfigurableApplicationContext;
 | 
				
			||||||
import org.springframework.context.annotation.Configuration;
 | 
					import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
import org.springframework.context.annotation.Import;
 | 
					import org.springframework.context.annotation.Import;
 | 
				
			||||||
| 
						 | 
					@ -45,6 +48,9 @@ public class LocalDeveloperToolsAutoConfigurationTests {
 | 
				
			||||||
	@Rule
 | 
						@Rule
 | 
				
			||||||
	public ExpectedException thrown = ExpectedException.none();
 | 
						public ExpectedException thrown = ExpectedException.none();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Rule
 | 
				
			||||||
 | 
						public MockRestarter mockRestarter = new MockRestarter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private int liveReloadPort = SocketUtils.findAvailableTcpPort();
 | 
						private int liveReloadPort = SocketUtils.findAvailableTcpPort();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private ConfigurableApplicationContext context;
 | 
						private ConfigurableApplicationContext context;
 | 
				
			||||||
| 
						 | 
					@ -70,6 +76,7 @@ public class LocalDeveloperToolsAutoConfigurationTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private ConfigurableApplicationContext initializeAndRun(Class<?> config,
 | 
						private ConfigurableApplicationContext initializeAndRun(Class<?> config,
 | 
				
			||||||
			Map<String, Object> properties) {
 | 
								Map<String, Object> properties) {
 | 
				
			||||||
 | 
							Restarter.initialize(new String[0], false, new MockRestartInitializer(), false);
 | 
				
			||||||
		SpringApplication application = new SpringApplication(config);
 | 
							SpringApplication application = new SpringApplication(config);
 | 
				
			||||||
		application.setDefaultProperties(getDefaultProperties(properties));
 | 
							application.setDefaultProperties(getDefaultProperties(properties));
 | 
				
			||||||
		application.setWebEnvironment(false);
 | 
							application.setWebEnvironment(false);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,76 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.Rule;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.junit.rules.TemporaryFolder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.equalTo;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for {@link ChangeableUrls}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class ChangeableUrlsTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Rule
 | 
				
			||||||
 | 
						public TemporaryFolder temporaryFolder = new TemporaryFolder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void folderUrl() throws Exception {
 | 
				
			||||||
 | 
							URL url = makeUrl("myproject");
 | 
				
			||||||
 | 
							assertThat(ChangeableUrls.fromUrls(url).size(), equalTo(1));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void fileUrl() throws Exception {
 | 
				
			||||||
 | 
							URL url = this.temporaryFolder.newFile().toURI().toURL();
 | 
				
			||||||
 | 
							assertThat(ChangeableUrls.fromUrls(url).size(), equalTo(0));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void httpUrl() throws Exception {
 | 
				
			||||||
 | 
							URL url = new URL("http://spring.io");
 | 
				
			||||||
 | 
							assertThat(ChangeableUrls.fromUrls(url).size(), equalTo(0));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void skipsUrls() throws Exception {
 | 
				
			||||||
 | 
							ChangeableUrls urls = ChangeableUrls
 | 
				
			||||||
 | 
									.fromUrls(makeUrl("spring-boot"), makeUrl("spring-boot-autoconfigure"),
 | 
				
			||||||
 | 
											makeUrl("spring-boot-actuator"), makeUrl("spring-boot-starter"),
 | 
				
			||||||
 | 
											makeUrl("spring-boot-starter-some-thing"));
 | 
				
			||||||
 | 
							assertThat(urls.size(), equalTo(0));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private URL makeUrl(String name) throws IOException {
 | 
				
			||||||
 | 
							File file = this.temporaryFolder.newFolder();
 | 
				
			||||||
 | 
							file = new File(file, name);
 | 
				
			||||||
 | 
							file = new File(file, "target");
 | 
				
			||||||
 | 
							file = new File(file, "classes");
 | 
				
			||||||
 | 
							file.mkdirs();
 | 
				
			||||||
 | 
							return file.toURI().toURL();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,130 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.equalTo;
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.not;
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.nullValue;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for {@link DefaultRestartInitializer}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class DefaultRestartInitializerTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void nullForTests() throws Exception {
 | 
				
			||||||
 | 
							MockRestartInitializer initializer = new MockRestartInitializer(true);
 | 
				
			||||||
 | 
							assertThat(initializer.getInitialUrls(Thread.currentThread()), nullValue());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void validMainThread() throws Exception {
 | 
				
			||||||
 | 
							MockRestartInitializer initializer = new MockRestartInitializer(false);
 | 
				
			||||||
 | 
							ClassLoader classLoader = new MockAppClassLoader(getClass().getClassLoader());
 | 
				
			||||||
 | 
							Thread thread = new Thread();
 | 
				
			||||||
 | 
							thread.setName("main");
 | 
				
			||||||
 | 
							thread.setContextClassLoader(classLoader);
 | 
				
			||||||
 | 
							assertThat(initializer.isMain(thread), equalTo(true));
 | 
				
			||||||
 | 
							assertThat(initializer.getInitialUrls(thread), not(nullValue()));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void threadNotNamedMain() throws Exception {
 | 
				
			||||||
 | 
							MockRestartInitializer initializer = new MockRestartInitializer(false);
 | 
				
			||||||
 | 
							ClassLoader classLoader = new MockAppClassLoader(getClass().getClassLoader());
 | 
				
			||||||
 | 
							Thread thread = new Thread();
 | 
				
			||||||
 | 
							thread.setName("buscuit");
 | 
				
			||||||
 | 
							thread.setContextClassLoader(classLoader);
 | 
				
			||||||
 | 
							assertThat(initializer.isMain(thread), equalTo(false));
 | 
				
			||||||
 | 
							assertThat(initializer.getInitialUrls(thread), nullValue());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void threadNotUsingAppClassLoader() throws Exception {
 | 
				
			||||||
 | 
							MockRestartInitializer initializer = new MockRestartInitializer(false);
 | 
				
			||||||
 | 
							ClassLoader classLoader = new MockLauncherClassLoader(getClass().getClassLoader());
 | 
				
			||||||
 | 
							Thread thread = new Thread();
 | 
				
			||||||
 | 
							thread.setName("main");
 | 
				
			||||||
 | 
							thread.setContextClassLoader(classLoader);
 | 
				
			||||||
 | 
							assertThat(initializer.isMain(thread), equalTo(false));
 | 
				
			||||||
 | 
							assertThat(initializer.getInitialUrls(thread), nullValue());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void skipsDueToJUnitStacks() throws Exception {
 | 
				
			||||||
 | 
							testSkipStack("org.junit.runners.Something", true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void skipsDueToSpringTest() throws Exception {
 | 
				
			||||||
 | 
							testSkipStack("org.springframework.boot.test.Something", true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void testSkipStack(String className, boolean expected) {
 | 
				
			||||||
 | 
							MockRestartInitializer initializer = new MockRestartInitializer(true);
 | 
				
			||||||
 | 
							StackTraceElement element = new StackTraceElement(className, "someMethod",
 | 
				
			||||||
 | 
									"someFile", 123);
 | 
				
			||||||
 | 
							assertThat(initializer.isSkippedStackElement(element), equalTo(expected));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class MockAppClassLoader extends ClassLoader {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public MockAppClassLoader(ClassLoader parent) {
 | 
				
			||||||
 | 
								super(parent);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class MockLauncherClassLoader extends ClassLoader {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public MockLauncherClassLoader(ClassLoader parent) {
 | 
				
			||||||
 | 
								super(parent);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class MockRestartInitializer extends DefaultRestartInitializer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final boolean considerStackElements;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public MockRestartInitializer(boolean considerStackElements) {
 | 
				
			||||||
 | 
								this.considerStackElements = considerStackElements;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							protected boolean isSkippedStackElement(StackTraceElement element) {
 | 
				
			||||||
 | 
								if (!this.considerStackElements) {
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							protected URL[] getUrls(Thread thread) {
 | 
				
			||||||
 | 
								return new URL[0];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,155 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.reflect.Method;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.junit.Rule;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.junit.rules.ExpectedException;
 | 
				
			||||||
 | 
					import org.springframework.util.ReflectionUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.equalTo;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for {@link MainMethod}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MainMethodTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Rule
 | 
				
			||||||
 | 
						public ExpectedException thrown = ExpectedException.none();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static ThreadLocal<MainMethod> mainMethod = new ThreadLocal<MainMethod>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Method actualMain;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Before
 | 
				
			||||||
 | 
						public void setup() throws Exception {
 | 
				
			||||||
 | 
							this.actualMain = Valid.class.getMethod("main", String[].class);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void threadMustNotBeNull() throws Exception {
 | 
				
			||||||
 | 
							this.thrown.expect(IllegalArgumentException.class);
 | 
				
			||||||
 | 
							this.thrown.expectMessage("Thread must not be null");
 | 
				
			||||||
 | 
							new MainMethod(null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void validMainMethod() throws Exception {
 | 
				
			||||||
 | 
							MainMethod method = new TestThread(new Runnable() {
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									Valid.main();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}).test();
 | 
				
			||||||
 | 
							assertThat(method.getMethod(), equalTo(this.actualMain));
 | 
				
			||||||
 | 
							assertThat(method.getDeclaringClassName(), equalTo(this.actualMain
 | 
				
			||||||
 | 
									.getDeclaringClass().getName()));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void missingArgsMainMethod() throws Exception {
 | 
				
			||||||
 | 
							this.thrown.expect(IllegalStateException.class);
 | 
				
			||||||
 | 
							this.thrown.expectMessage("Unable to find main method");
 | 
				
			||||||
 | 
							new TestThread(new Runnable() {
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									MissingArgs.main();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}).test();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void nonStatic() throws Exception {
 | 
				
			||||||
 | 
							this.thrown.expect(IllegalStateException.class);
 | 
				
			||||||
 | 
							this.thrown.expectMessage("Unable to find main method");
 | 
				
			||||||
 | 
							new TestThread(new Runnable() {
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									new NonStaticMain().main();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}).test();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class TestThread extends Thread {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final Runnable runnable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private Exception exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private MainMethod mainMethod;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public TestThread(Runnable runnable) {
 | 
				
			||||||
 | 
								this.runnable = runnable;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public MainMethod test() throws InterruptedException {
 | 
				
			||||||
 | 
								start();
 | 
				
			||||||
 | 
								join();
 | 
				
			||||||
 | 
								if (this.exception != null) {
 | 
				
			||||||
 | 
									ReflectionUtils.rethrowRuntimeException(this.exception);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return this.mainMethod;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public void run() {
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									this.runnable.run();
 | 
				
			||||||
 | 
									this.mainMethod = MainMethodTests.mainMethod.get();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (Exception ex) {
 | 
				
			||||||
 | 
									this.exception = ex;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static class Valid {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public static void main(String... args) {
 | 
				
			||||||
 | 
								someOtherMethod();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private static void someOtherMethod() {
 | 
				
			||||||
 | 
								mainMethod.set(new MainMethod());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static class MissingArgs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public static void main() {
 | 
				
			||||||
 | 
								mainMethod.set(new MainMethod());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class NonStaticMain {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public void main(String... args) {
 | 
				
			||||||
 | 
								mainMethod.set(new MainMethod());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.boot.developertools.restart.RestartInitializer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Simple mock {@link RestartInitializer} that returns an empty array of URLs.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MockRestartInitializer implements RestartInitializer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public URL[] getInitialUrls(Thread thread) {
 | 
				
			||||||
 | 
							return new URL[] {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.concurrent.ThreadFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.rules.TestRule;
 | 
				
			||||||
 | 
					import org.junit.runner.Description;
 | 
				
			||||||
 | 
					import org.junit.runners.model.Statement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.mockito.BDDMockito.given;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Mocked version of {@link Restarter}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MockRestarter implements TestRule {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Map<String, Object> attributes = new HashMap<String, Object>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Restarter mock = mock(Restarter.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public Statement apply(final Statement base, Description description) {
 | 
				
			||||||
 | 
							return new Statement() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void evaluate() throws Throwable {
 | 
				
			||||||
 | 
									setup();
 | 
				
			||||||
 | 
									base.evaluate();
 | 
				
			||||||
 | 
									cleanup();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void setup() {
 | 
				
			||||||
 | 
							Restarter.setInstance(this.mock);
 | 
				
			||||||
 | 
							given(this.mock.getInitialUrls()).willReturn(new URL[] {});
 | 
				
			||||||
 | 
							given(this.mock.getThreadFactory()).willReturn(new ThreadFactory() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public Thread newThread(Runnable r) {
 | 
				
			||||||
 | 
									return new Thread(r);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void cleanup() {
 | 
				
			||||||
 | 
							this.attributes.clear();
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Restarter getMock() {
 | 
				
			||||||
 | 
							return this.mock;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,112 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.After;
 | 
				
			||||||
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.springframework.context.ConfigurableApplicationContext;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.equalTo;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					import static org.mockito.BDDMockito.given;
 | 
				
			||||||
 | 
					import static org.mockito.Matchers.any;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for {@link OnInitializedRestarterCondition}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class OnInitializedRestarterConditionTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static Object wait = new Object();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Before
 | 
				
			||||||
 | 
						@After
 | 
				
			||||||
 | 
						public void cleanup() {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void noInstance() throws Exception {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
							ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
 | 
				
			||||||
 | 
									Config.class);
 | 
				
			||||||
 | 
							assertThat(context.containsBean("bean"), equalTo(false));
 | 
				
			||||||
 | 
							context.close();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void noInitialization() throws Exception {
 | 
				
			||||||
 | 
							Restarter.initialize(new String[0], false, RestartInitializer.NONE);
 | 
				
			||||||
 | 
							ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
 | 
				
			||||||
 | 
									Config.class);
 | 
				
			||||||
 | 
							assertThat(context.containsBean("bean"), equalTo(false));
 | 
				
			||||||
 | 
							context.close();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void initialized() throws Exception {
 | 
				
			||||||
 | 
							Thread thread = new Thread() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									TestInitialized.main();
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							thread.start();
 | 
				
			||||||
 | 
							synchronized (wait) {
 | 
				
			||||||
 | 
								wait.wait();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static class TestInitialized {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public static void main(String... args) {
 | 
				
			||||||
 | 
								RestartInitializer initializer = mock(RestartInitializer.class);
 | 
				
			||||||
 | 
								given(initializer.getInitialUrls((Thread) any())).willReturn(new URL[0]);
 | 
				
			||||||
 | 
								Restarter.initialize(new String[0], false, initializer);
 | 
				
			||||||
 | 
								ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
 | 
				
			||||||
 | 
										Config.class);
 | 
				
			||||||
 | 
								assertThat(context.containsBean("bean"), equalTo(true));
 | 
				
			||||||
 | 
								context.close();
 | 
				
			||||||
 | 
								synchronized (wait) {
 | 
				
			||||||
 | 
									wait.notify();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Configuration
 | 
				
			||||||
 | 
						public static class Config {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Bean
 | 
				
			||||||
 | 
							@ConditionalOnInitializedRestarter
 | 
				
			||||||
 | 
							public String bean() {
 | 
				
			||||||
 | 
								return "bean";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,87 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.After;
 | 
				
			||||||
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.springframework.boot.SpringApplication;
 | 
				
			||||||
 | 
					import org.springframework.boot.context.event.ApplicationFailedEvent;
 | 
				
			||||||
 | 
					import org.springframework.boot.context.event.ApplicationReadyEvent;
 | 
				
			||||||
 | 
					import org.springframework.boot.context.event.ApplicationStartedEvent;
 | 
				
			||||||
 | 
					import org.springframework.context.ConfigurableApplicationContext;
 | 
				
			||||||
 | 
					import org.springframework.core.Ordered;
 | 
				
			||||||
 | 
					import org.springframework.test.util.ReflectionTestUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.equalTo;
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.not;
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.nullValue;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for {@link RestartApplicationListener}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class RestartApplicationListenerTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Before
 | 
				
			||||||
 | 
						@After
 | 
				
			||||||
 | 
						public void cleanup() {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void isHighestPriority() throws Exception {
 | 
				
			||||||
 | 
							assertThat(new RestartApplicationListener().getOrder(),
 | 
				
			||||||
 | 
									equalTo(Ordered.HIGHEST_PRECEDENCE));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void initializeWithReady() throws Exception {
 | 
				
			||||||
 | 
							testInitialize(false);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void initializeWithFail() throws Exception {
 | 
				
			||||||
 | 
							testInitialize(true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void testInitialize(boolean failed) {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
							RestartApplicationListener listener = new RestartApplicationListener();
 | 
				
			||||||
 | 
							SpringApplication application = new SpringApplication();
 | 
				
			||||||
 | 
							ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
 | 
				
			||||||
 | 
							String[] args = new String[] { "a", "b", "c" };
 | 
				
			||||||
 | 
							listener.onApplicationEvent(new ApplicationStartedEvent(application, args));
 | 
				
			||||||
 | 
							assertThat(Restarter.getInstance(), not(nullValue()));
 | 
				
			||||||
 | 
							assertThat(Restarter.getInstance().isFinished(), equalTo(false));
 | 
				
			||||||
 | 
							assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "args"),
 | 
				
			||||||
 | 
									equalTo((Object) args));
 | 
				
			||||||
 | 
							if (failed) {
 | 
				
			||||||
 | 
								listener.onApplicationEvent(new ApplicationFailedEvent(application, args,
 | 
				
			||||||
 | 
										context, new RuntimeException()));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								listener.onApplicationEvent(new ApplicationReadyEvent(application, args,
 | 
				
			||||||
 | 
										context));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							assertThat(Restarter.getInstance().isFinished(), equalTo(true));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,194 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					import java.net.URLClassLoader;
 | 
				
			||||||
 | 
					import java.util.concurrent.ThreadFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.After;
 | 
				
			||||||
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.junit.Rule;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.junit.rules.ExpectedException;
 | 
				
			||||||
 | 
					import org.springframework.boot.test.OutputCapture;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.EnableScheduling;
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.Scheduled;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					import org.springframework.util.StringUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.equalTo;
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.greaterThan;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					import static org.mockito.BDDMockito.given;
 | 
				
			||||||
 | 
					import static org.mockito.Matchers.any;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for {@link Restarter}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class RestarterTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Rule
 | 
				
			||||||
 | 
						public ExpectedException thrown = ExpectedException.none();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Rule
 | 
				
			||||||
 | 
						public OutputCapture out = new OutputCapture();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Before
 | 
				
			||||||
 | 
						public void setup() {
 | 
				
			||||||
 | 
							Restarter.setInstance(new TestableRestarter());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@After
 | 
				
			||||||
 | 
						public void cleanup() {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void cantGetInstanceBeforeInitialize() throws Exception {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
							this.thrown.expect(IllegalStateException.class);
 | 
				
			||||||
 | 
							this.thrown.expectMessage("Restarter has not been initialized");
 | 
				
			||||||
 | 
							Restarter.getInstance();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void testRestart() throws Exception {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
							Thread thread = new Thread() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									SampleApplication.main();
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							thread.start();
 | 
				
			||||||
 | 
							Thread.sleep(1600);
 | 
				
			||||||
 | 
							String output = this.out.toString();
 | 
				
			||||||
 | 
							assertThat(StringUtils.countOccurrencesOf(output, "Tick 0"), greaterThan(2));
 | 
				
			||||||
 | 
							assertThat(StringUtils.countOccurrencesOf(output, "Tick 1"), greaterThan(2));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void getThreadFactory() throws Exception {
 | 
				
			||||||
 | 
							final ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
 | 
				
			||||||
 | 
							final ClassLoader contextClassLoader = new URLClassLoader(new URL[0]);
 | 
				
			||||||
 | 
							Thread thread = new Thread() {
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									Runnable runnable = mock(Runnable.class);
 | 
				
			||||||
 | 
									Thread regular = new Thread();
 | 
				
			||||||
 | 
									ThreadFactory factory = Restarter.getInstance().getThreadFactory();
 | 
				
			||||||
 | 
									Thread viaFactory = factory.newThread(runnable);
 | 
				
			||||||
 | 
									// Regular threads will inherit the current thread
 | 
				
			||||||
 | 
									assertThat(regular.getContextClassLoader(), equalTo(contextClassLoader));
 | 
				
			||||||
 | 
									// Factory threads should should inherit from the initial thread
 | 
				
			||||||
 | 
									assertThat(viaFactory.getContextClassLoader(), equalTo(parentLoader));
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							thread.setContextClassLoader(contextClassLoader);
 | 
				
			||||||
 | 
							thread.start();
 | 
				
			||||||
 | 
							thread.join();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void getInitialUrls() throws Exception {
 | 
				
			||||||
 | 
							Restarter.clearInstance();
 | 
				
			||||||
 | 
							RestartInitializer initializer = mock(RestartInitializer.class);
 | 
				
			||||||
 | 
							URL[] urls = new URL[] { new URL("file:/proj/module-a.jar!/") };
 | 
				
			||||||
 | 
							given(initializer.getInitialUrls(any(Thread.class))).willReturn(urls);
 | 
				
			||||||
 | 
							Restarter.initialize(new String[0], false, initializer, false);
 | 
				
			||||||
 | 
							assertThat(Restarter.getInstance().getInitialUrls(), equalTo(urls));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Component
 | 
				
			||||||
 | 
						@EnableScheduling
 | 
				
			||||||
 | 
						public static class SampleApplication {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private int count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private static volatile boolean quit = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Scheduled(fixedDelay = 100)
 | 
				
			||||||
 | 
							public void tickBean() {
 | 
				
			||||||
 | 
								System.out.println("Tick " + this.count++ + " " + Thread.currentThread());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Scheduled(initialDelay = 350, fixedDelay = 350)
 | 
				
			||||||
 | 
							public void restart() {
 | 
				
			||||||
 | 
								System.out.println("Restart " + Thread.currentThread());
 | 
				
			||||||
 | 
								if (!SampleApplication.quit) {
 | 
				
			||||||
 | 
									Restarter.getInstance().restart();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public static void main(String... args) {
 | 
				
			||||||
 | 
								Restarter.initialize(args, false, new MockRestartInitializer());
 | 
				
			||||||
 | 
								AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
 | 
				
			||||||
 | 
										SampleApplication.class);
 | 
				
			||||||
 | 
								context.registerShutdownHook();
 | 
				
			||||||
 | 
								System.out.println("Sleep " + Thread.currentThread());
 | 
				
			||||||
 | 
								sleep();
 | 
				
			||||||
 | 
								quit = true;
 | 
				
			||||||
 | 
								context.close();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private static void sleep() {
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									Thread.sleep(1200);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (InterruptedException ex) {
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class TestableRestarter extends Restarter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public TestableRestarter() {
 | 
				
			||||||
 | 
								this(Thread.currentThread(), new String[] {}, false,
 | 
				
			||||||
 | 
										new MockRestartInitializer());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							protected TestableRestarter(Thread thread, String[] args,
 | 
				
			||||||
 | 
									boolean forceReferenceCleanup, RestartInitializer initializer) {
 | 
				
			||||||
 | 
								super(thread, args, forceReferenceCleanup, initializer);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public void restart() {
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									stop();
 | 
				
			||||||
 | 
									start();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (Exception ex) {
 | 
				
			||||||
 | 
									throw new IllegalStateException(ex);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							protected void stop() throws Exception {
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,84 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2012-2015 the original author or authors.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					 * You may obtain a copy of the License at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					 * limitations under the License.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.springframework.boot.developertools.restart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.equalTo;
 | 
				
			||||||
 | 
					import static org.hamcrest.Matchers.nullValue;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					import static org.junit.Assert.fail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tests for {@link SilentExitExceptionHandler}.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Phillip Webb
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class SilentExitExceptionHandlerTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void setupAndExit() throws Exception {
 | 
				
			||||||
 | 
							TestThread testThread = new TestThread() {
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									SilentExitExceptionHandler.exitCurrentThread();
 | 
				
			||||||
 | 
									fail("Didn't exit");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							SilentExitExceptionHandler.setup(testThread);
 | 
				
			||||||
 | 
							testThread.startAndJoin();
 | 
				
			||||||
 | 
							assertThat(testThread.getThrown(), nullValue());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void doesntInterferWithOtherExceptions() throws Exception {
 | 
				
			||||||
 | 
							TestThread testThread = new TestThread() {
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run() {
 | 
				
			||||||
 | 
									throw new IllegalStateException("Expected");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							SilentExitExceptionHandler.setup(testThread);
 | 
				
			||||||
 | 
							testThread.startAndJoin();
 | 
				
			||||||
 | 
							assertThat(testThread.getThrown().getMessage(), equalTo("Expected"));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static abstract class TestThread extends Thread {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private Throwable thrown;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public TestThread() {
 | 
				
			||||||
 | 
								setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
 | 
				
			||||||
 | 
									@Override
 | 
				
			||||||
 | 
									public void uncaughtException(Thread t, Throwable e) {
 | 
				
			||||||
 | 
										TestThread.this.thrown = e;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public Throwable getThrown() {
 | 
				
			||||||
 | 
								return this.thrown;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public void startAndJoin() throws InterruptedException {
 | 
				
			||||||
 | 
								start();
 | 
				
			||||||
 | 
								join();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue