001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.loader;
018
019import java.io.File;
020import java.net.URI;
021import java.net.URL;
022import java.security.CodeSource;
023import java.security.ProtectionDomain;
024import java.util.ArrayList;
025import java.util.List;
026
027import org.springframework.boot.loader.archive.Archive;
028import org.springframework.boot.loader.archive.ExplodedArchive;
029import org.springframework.boot.loader.archive.JarFileArchive;
030import org.springframework.boot.loader.jar.JarFile;
031
032/**
033 * Base class for launchers that can start an application with a fully configured
034 * classpath backed by one or more {@link Archive}s.
035 *
036 * @author Phillip Webb
037 * @author Dave Syer
038 */
039public abstract class Launcher {
040
041        /**
042         * Launch the application. This method is the initial entry point that should be
043         * called by a subclass {@code public static void main(String[] args)} method.
044         * @param args the incoming arguments
045         * @throws Exception if the application fails to launch
046         */
047        protected void launch(String[] args) throws Exception {
048                JarFile.registerUrlProtocolHandler();
049                ClassLoader classLoader = createClassLoader(getClassPathArchives());
050                launch(args, getMainClass(), classLoader);
051        }
052
053        /**
054         * Create a classloader for the specified archives.
055         * @param archives the archives
056         * @return the classloader
057         * @throws Exception if the classloader cannot be created
058         */
059        protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
060                List<URL> urls = new ArrayList<>(archives.size());
061                for (Archive archive : archives) {
062                        urls.add(archive.getUrl());
063                }
064                return createClassLoader(urls.toArray(new URL[0]));
065        }
066
067        /**
068         * Create a classloader for the specified URLs.
069         * @param urls the URLs
070         * @return the classloader
071         * @throws Exception if the classloader cannot be created
072         */
073        protected ClassLoader createClassLoader(URL[] urls) throws Exception {
074                return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
075        }
076
077        /**
078         * Launch the application given the archive file and a fully configured classloader.
079         * @param args the incoming arguments
080         * @param mainClass the main class to run
081         * @param classLoader the classloader
082         * @throws Exception if the launch fails
083         */
084        protected void launch(String[] args, String mainClass, ClassLoader classLoader)
085                        throws Exception {
086                Thread.currentThread().setContextClassLoader(classLoader);
087                createMainMethodRunner(mainClass, args, classLoader).run();
088        }
089
090        /**
091         * Create the {@code MainMethodRunner} used to launch the application.
092         * @param mainClass the main class
093         * @param args the incoming arguments
094         * @param classLoader the classloader
095         * @return the main method runner
096         */
097        protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
098                        ClassLoader classLoader) {
099                return new MainMethodRunner(mainClass, args);
100        }
101
102        /**
103         * Returns the main class that should be launched.
104         * @return the name of the main class
105         * @throws Exception if the main class cannot be obtained
106         */
107        protected abstract String getMainClass() throws Exception;
108
109        /**
110         * Returns the archives that will be used to construct the class path.
111         * @return the class path archives
112         * @throws Exception if the class path archives cannot be obtained
113         */
114        protected abstract List<Archive> getClassPathArchives() throws Exception;
115
116        protected final Archive createArchive() throws Exception {
117                ProtectionDomain protectionDomain = getClass().getProtectionDomain();
118                CodeSource codeSource = protectionDomain.getCodeSource();
119                URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
120                String path = (location != null) ? location.getSchemeSpecificPart() : null;
121                if (path == null) {
122                        throw new IllegalStateException("Unable to determine code source archive");
123                }
124                File root = new File(path);
125                if (!root.exists()) {
126                        throw new IllegalStateException(
127                                        "Unable to determine code source archive from " + root);
128                }
129                return (root.isDirectory() ? new ExplodedArchive(root)
130                                : new JarFileArchive(root));
131        }
132
133}