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;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 | 
			
		||||
import org.springframework.boot.developertools.restart.ConditionalOnInitializedRestarter;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +28,7 @@ import org.springframework.context.annotation.Configuration;
 | 
			
		|||
 * @since 1.3.0
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
@ConditionalOnInitializedRestarter
 | 
			
		||||
public class LocalDeveloperToolsAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
	@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
 | 
			
		||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 | 
			
		||||
org.springframework.boot.developertools.autoconfigure.LocalDeveloperToolsAutoConfiguration
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,9 @@ import org.junit.Test;
 | 
			
		|||
import org.junit.rules.ExpectedException;
 | 
			
		||||
import org.springframework.boot.SpringApplication;
 | 
			
		||||
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.annotation.Configuration;
 | 
			
		||||
import org.springframework.context.annotation.Import;
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +48,9 @@ public class LocalDeveloperToolsAutoConfigurationTests {
 | 
			
		|||
	@Rule
 | 
			
		||||
	public ExpectedException thrown = ExpectedException.none();
 | 
			
		||||
 | 
			
		||||
	@Rule
 | 
			
		||||
	public MockRestarter mockRestarter = new MockRestarter();
 | 
			
		||||
 | 
			
		||||
	private int liveReloadPort = SocketUtils.findAvailableTcpPort();
 | 
			
		||||
 | 
			
		||||
	private ConfigurableApplicationContext context;
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +76,7 @@ public class LocalDeveloperToolsAutoConfigurationTests {
 | 
			
		|||
 | 
			
		||||
	private ConfigurableApplicationContext initializeAndRun(Class<?> config,
 | 
			
		||||
			Map<String, Object> properties) {
 | 
			
		||||
		Restarter.initialize(new String[0], false, new MockRestartInitializer(), false);
 | 
			
		||||
		SpringApplication application = new SpringApplication(config);
 | 
			
		||||
		application.setDefaultProperties(getDefaultProperties(properties));
 | 
			
		||||
		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