001/*
002 * Copyright 2012-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.devtools.restart;
018
019import java.net.URL;
020import java.net.URLClassLoader;
021import java.util.Collections;
022import java.util.LinkedHashSet;
023import java.util.Set;
024
025/**
026 * Default {@link RestartInitializer} that only enable initial restart when running a
027 * standard "main" method. Skips initialization when running "fat" jars (included
028 * exploded) or when running from a test.
029 *
030 * @author Phillip Webb
031 * @author Andy Wilkinson
032 * @since 1.3.0
033 */
034public class DefaultRestartInitializer implements RestartInitializer {
035
036        private static final Set<String> SKIPPED_STACK_ELEMENTS;
037
038        static {
039                Set<String> skipped = new LinkedHashSet<String>();
040                skipped.add("org.junit.runners.");
041                skipped.add("org.springframework.boot.test.");
042                skipped.add("cucumber.runtime.");
043                SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped);
044        }
045
046        @Override
047        public URL[] getInitialUrls(Thread thread) {
048                if (!isMain(thread)) {
049                        return null;
050                }
051                for (StackTraceElement element : thread.getStackTrace()) {
052                        if (isSkippedStackElement(element)) {
053                                return null;
054                        }
055                }
056                return getUrls(thread);
057        }
058
059        /**
060         * Returns if the thread is for a main invocation. By default checks the name of the
061         * thread and the context classloader.
062         * @param thread the thread to check
063         * @return {@code true} if the thread is a main invocation
064         */
065        protected boolean isMain(Thread thread) {
066                return thread.getName().equals("main") && thread.getContextClassLoader()
067                                .getClass().getName().contains("AppClassLoader");
068        }
069
070        /**
071         * Checks if a specific {@link StackTraceElement} should cause the initializer to be
072         * skipped.
073         * @param element the stack element to check
074         * @return {@code true} if the stack element means that the initializer should be
075         * skipped
076         */
077        protected boolean isSkippedStackElement(StackTraceElement element) {
078                for (String skipped : SKIPPED_STACK_ELEMENTS) {
079                        if (element.getClassName().startsWith(skipped)) {
080                                return true;
081                        }
082                }
083                return false;
084        }
085
086        /**
087         * Return the URLs that should be used with initialization.
088         * @param thread the source thread
089         * @return the URLs
090         */
091        protected URL[] getUrls(Thread thread) {
092                return ChangeableUrls
093                                .fromUrlClassLoader((URLClassLoader) thread.getContextClassLoader())
094                                .toArray();
095        }
096
097}