diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java index c149774ca4..fffbf484a5 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java @@ -28,13 +28,10 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; -import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; import java.util.jar.Manifest; import joptsimple.OptionSet; @@ -58,8 +55,10 @@ import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter; import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory; import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; -import org.springframework.boot.loader.ArchiveResolver; +import org.springframework.boot.cli.jar.PackagedSpringApplicationLauncher; import org.springframework.boot.loader.tools.JarWriter; +import org.springframework.boot.loader.tools.Layout; +import org.springframework.boot.loader.tools.Layouts; import org.springframework.util.StringUtils; /** @@ -69,6 +68,8 @@ import org.springframework.util.StringUtils; */ public class JarCommand extends OptionParsingCommand { + private static final Layout LAYOUT = new Layouts.Jar(); + public JarCommand() { super( "jar", @@ -146,8 +147,11 @@ public class JarCommand extends OptionParsingCommand { addDependencies(jarWriter, dependencyUrls); addClasspathEntries(jarWriter, classpathEntries); addApplicationClasses(jarWriter, compiledClasses); + String runnerClassName = getClassFile(PackagedSpringApplicationLauncher.class + .getName()); + jarWriter.writeEntry(runnerClassName, + getClass().getResourceAsStream("/" + runnerClassName)); jarWriter.writeLoaderClasses(); - addJarRunner(jarWriter); } finally { jarWriter.close(); @@ -206,9 +210,10 @@ public class JarCommand extends OptionParsingCommand { "Application-Classes", StringUtils.collectionToCommaDelimitedString(compiledClasses .keySet())); - - manifest.getMainAttributes() - .putValue("Main-Class", JarRunner.class.getName()); + manifest.getMainAttributes().putValue("Main-Class", + LAYOUT.getLauncherClassName()); + manifest.getMainAttributes().putValue("Start-Class", + PackagedSpringApplicationLauncher.class.getName()); return manifest; } @@ -243,27 +248,14 @@ public class JarCommand extends OptionParsingCommand { final Map compiledClasses) throws IOException { for (Entry entry : compiledClasses.entrySet()) { - String className = entry.getKey().replace(".", "/") + ".class"; - jarWriter.writeEntry(className, + jarWriter.writeEntry(getClassFile(entry.getKey()), new ByteArrayInputStream(entry.getValue())); } } - private void addJarRunner(JarWriter jar) throws IOException, URISyntaxException { - JarFile jarFile = getJarFile(); - String namePrefix = JarRunner.class.getName().replace(".", "/"); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (entry.getName().startsWith(namePrefix)) { - jar.writeEntry(entry.getName(), jarFile.getInputStream(entry)); - } - } + private String getClassFile(String className) { + return className.replace(".", "/") + ".class"; } - private JarFile getJarFile() throws URISyntaxException, IOException { - return new JarFile( - new ArchiveResolver().resolveArchiveLocation(JarCommand.class)); - } } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarRunner.java deleted file mode 100644 index 6b5024bad3..0000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarRunner.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.cli.command.jar; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.boot.loader.ArchiveResolver; -import org.springframework.boot.loader.AsciiBytes; -import org.springframework.boot.loader.LaunchedURLClassLoader; -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.Archive.Entry; -import org.springframework.boot.loader.archive.Archive.EntryFilter; - -/** - * A runner for a CLI application that has been compiled and packaged as a jar file - * - * @author Andy Wilkinson - */ -public class JarRunner { - - private static final AsciiBytes LIB = new AsciiBytes("lib/"); - - public static void main(String[] args) throws URISyntaxException, IOException, - ClassNotFoundException, SecurityException, NoSuchMethodException, - IllegalArgumentException, IllegalAccessException, InvocationTargetException { - - Archive archive = new ArchiveResolver().resolveArchive(JarRunner.class); - - ClassLoader classLoader = createClassLoader(archive); - Class[] classes = loadApplicationClasses(archive, classLoader); - - Thread.currentThread().setContextClassLoader(classLoader); - - // Use reflection to load and call Spring - Class application = classLoader - .loadClass("org.springframework.boot.SpringApplication"); - Method method = application.getMethod("run", Object[].class, String[].class); - method.invoke(null, classes, args); - - } - - private static ClassLoader createClassLoader(Archive archive) throws IOException, - MalformedURLException { - List nestedArchives = archive.getNestedArchives(new EntryFilter() { - - @Override - public boolean matches(Entry entry) { - return entry.getName().startsWith(LIB); - } - - }); - - List urls = new ArrayList(); - urls.add(archive.getUrl()); - for (Archive nestedArchive : nestedArchives) { - urls.add(nestedArchive.getUrl()); - } - - ClassLoader classLoader = new LaunchedURLClassLoader(urls.toArray(new URL[urls - .size()]), JarRunner.class.getClassLoader()); - return classLoader; - } - - private static Class[] loadApplicationClasses(Archive archive, - ClassLoader classLoader) throws ClassNotFoundException, IOException { - String[] classNames = archive.getManifest().getMainAttributes() - .getValue("Application-Classes").split(","); - - Class[] classes = new Class[classNames.length]; - - for (int i = 0; i < classNames.length; i++) { - Class applicationClass = classLoader.loadClass(classNames[i]); - classes[i] = applicationClass; - } - return classes; - } -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java new file mode 100644 index 0000000000..f59086a2f9 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java @@ -0,0 +1,62 @@ +/* + * 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.cli.jar; + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.jar.Manifest; + +/** + * A launcher for a CLI application that has been compiled and packaged as a jar file. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +public class PackagedSpringApplicationLauncher { + + private static final String SPRING_APPLICATION_CLASS = "org.springframework.boot.SpringApplication"; + + private void run(String[] args) throws Exception { + URLClassLoader classLoader = (URLClassLoader) Thread.currentThread() + .getContextClassLoader(); + Class application = classLoader.loadClass(SPRING_APPLICATION_CLASS); + Method method = application.getMethod("run", Object[].class, String[].class); + method.invoke(null, getSources(classLoader), args); + } + + private Object[] getSources(URLClassLoader classLoader) throws Exception { + URL url = classLoader.findResource("META-INF/MANIFEST.MF"); + Manifest manifest = new Manifest(url.openStream()); + String attribute = manifest.getMainAttributes().getValue("Application-Classes"); + return loadClasses(classLoader, attribute.split(",")); + } + + private Class[] loadClasses(ClassLoader classLoader, String[] names) + throws ClassNotFoundException { + Class[] classes = new Class[names.length]; + for (int i = 0; i < names.length; i++) { + classes[i] = classLoader.loadClass(names[i]); + } + return classes; + } + + public static void main(String[] args) throws Exception { + new PackagedSpringApplicationLauncher().run(args); + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/package-info.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/package-info.java new file mode 100644 index 0000000000..6140e0ac0c --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Class that are packaged as part of CLI generated JARs. + * @see org.springframework.boot.cli.command.jar.JarCommand + */ +package org.springframework.boot.cli.jar; + diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java index 9bee0d2b1c..dbef42207f 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java @@ -44,6 +44,7 @@ import java.util.zip.ZipEntry; * items are ignored. * * @author Phillip Webb + * @author Andy Wilkinson */ public class JarWriter { @@ -109,7 +110,6 @@ public class JarWriter { /** * Writes an entry. The {@code inputStream} is closed once the entry has been written - * * @param entryName The name of the entry * @param inputStream The stream from which the entry's data can be read * @throws IOException if the write fails @@ -123,7 +123,7 @@ public class JarWriter { * Write a nested library. * @param destination the destination of the library * @param file the library file - * @throws IOException + * @throws IOException if the write fails */ public void writeNestedLibrary(String destination, File file) throws IOException { JarEntry entry = new JarEntry(destination + file.getName()); diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveResolver.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveResolver.java deleted file mode 100644 index 5d1d48db18..0000000000 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveResolver.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.loader; - -import java.io.File; -import java.io.IOException; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.security.CodeSource; -import java.security.ProtectionDomain; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.ExplodedArchive; -import org.springframework.boot.loader.archive.JarFileArchive; - -/** - * Resolves the {@link Archive} from which a {@link Class} was loaded. - * - * @author Andy Wilkinson - * @author Phillip Webb - */ -public class ArchiveResolver { - - /** - * Resolves the {@link Archive} that contains the given {@code clazz}. - * @param clazz The class whose containing archive is to be resolved - * - * @return The class's containing archive - * @throws IOException if an error occurs when resolving the containing archive - */ - public Archive resolveArchive(Class clazz) throws IOException { - File root = resolveArchiveLocation(clazz); - return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); - } - - /** - * Resolves the location of the archive that contains the given {@code clazz}. - * @param clazz The class for which the location of the containing archive is to be - * resolved - * - * @return The location of the class's containing archive - * @throws IOException if an error occurs when resolving the containing archive's - * location - */ - public File resolveArchiveLocation(Class clazz) throws IOException { - ProtectionDomain protectionDomain = getClass().getProtectionDomain(); - CodeSource codeSource = protectionDomain.getCodeSource(); - - if (codeSource != null) { - File root; - URL location = codeSource.getLocation(); - URLConnection connection = location.openConnection(); - if (connection instanceof JarURLConnection) { - root = new File(((JarURLConnection) connection).getJarFile().getName()); - } - else { - root = new File(location.getPath()); - } - - if (!root.exists()) { - throw new IllegalStateException( - "Unable to determine code source archive from " + root); - } - return root; - } - throw new IllegalStateException("Unable to determine code source archive"); - } -} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java index 05df0f5ae8..1a4dbb0c7f 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -16,6 +16,10 @@ package org.springframework.boot.loader; +import java.io.File; +import java.net.URI; +import java.security.CodeSource; +import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import java.util.jar.JarEntry; @@ -23,6 +27,8 @@ import java.util.jar.JarEntry; import org.springframework.boot.loader.archive.Archive; import org.springframework.boot.loader.archive.Archive.Entry; import org.springframework.boot.loader.archive.Archive.EntryFilter; +import org.springframework.boot.loader.archive.ExplodedArchive; +import org.springframework.boot.loader.archive.JarFileArchive; /** * Base class for executable archive {@link Launcher}s. @@ -43,7 +49,19 @@ public abstract class ExecutableArchiveLauncher extends Launcher { } private Archive createArchive() throws Exception { - return new ArchiveResolver().resolveArchive(getClass()); + ProtectionDomain protectionDomain = getClass().getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); + String path = (location == null ? null : location.getPath()); + if (path == null) { + throw new IllegalStateException("Unable to determine code source archive"); + } + File root = new File(path); + if (!root.exists()) { + throw new IllegalStateException( + "Unable to determine code source archive from " + root); + } + return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } protected final Archive getArchive() { diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java index f166d743df..508427732c 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java @@ -21,10 +21,13 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; +import java.security.CodeSource; +import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -494,7 +497,19 @@ public class PropertiesLauncher extends Launcher { } private Archive createArchive() throws Exception { - return new ArchiveResolver().resolveArchive(getClass()); + ProtectionDomain protectionDomain = getClass().getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); + String path = (location == null ? null : location.getPath()); + if (path == null) { + throw new IllegalStateException("Unable to determine code source archive"); + } + File root = new File(path); + if (!root.exists()) { + throw new IllegalStateException( + "Unable to determine code source archive from " + root); + } + return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } private void addParentClassLoaderEntries(List lib) throws IOException,