diff --git a/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc b/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc index 6b662acd1a..55449e04e6 100644 --- a/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc +++ b/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc @@ -493,7 +493,7 @@ The following configuration options are available: [[build-tool-plugins-gradle-configuration-layouts]] -==== Available layouts +==== Available built-in layouts The `layout` attribute configures the format of the archive and whether the bootstrap loader should be included or not. The following layouts are available: @@ -530,6 +530,37 @@ loader should be included or not. The following layouts are available: +[[build-tool-plugins-gradle-configuration-custom-layout]] +==== Using a custom layout +If you have custom requirements for how to arrange the dependencies and loader classes +inside the repackaged jar, you can use a custom layout in addition to the built-in values. +Any library which defines one or more `LayoutFactory` implementations and +lists them in `META-INF/spring.factories` can be added to the build script dependencies +and then the layout type becomes available in the `springBoot` configuration. For example + +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +buildscript { + ext { + springBootVersion = '1.5.0.BUILD-SNAPSHOT' + customVersion = '0.0.1.BUILD-SNAPSHOT' + } + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath("com.example:custom-layout:${customVersion}") + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +springBoot { + layout = 'CUSTOM' +} +---- + + [[build-tool-plugins-understanding-the-gradle-plugin]] === Understanding how the Gradle plugin works When `spring-boot` is applied to your Gradle project a default task named `bootRepackage` diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java index 987336250c..d84bb2134c 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/SpringBootPluginExtension.java @@ -21,12 +21,13 @@ import java.util.Map; import java.util.Set; import groovy.lang.Closure; + import org.gradle.api.Project; import org.gradle.api.plugins.JavaPlugin; import org.springframework.boot.gradle.buildinfo.BuildInfo; import org.springframework.boot.loader.tools.Layout; -import org.springframework.boot.loader.tools.Layouts; +import org.springframework.boot.loader.tools.LayoutType; /** * Gradle DSL Extension for 'Spring Boot'. Most of the time Spring Boot can guess the @@ -88,7 +89,7 @@ public class SpringBootPluginExtension { * the MANIFEST.MF 'Main-Class' to be PropertiesLauncher. Gradle will coerce literal * String values to the correct type. */ - LayoutType layout; + String layout; /** * Libraries that must be unpacked from fat jars in order to run. Use Strings in the @@ -145,7 +146,7 @@ public class SpringBootPluginExtension { * @return the Layout to use or null if not explicitly set */ public Layout convertLayout() { - return (this.layout == null ? null : this.layout.layout); + return (this.layout == null ? null : LayoutType.layout(this.layout)); } public String getMainClass() { @@ -188,11 +189,11 @@ public class SpringBootPluginExtension { this.backupSource = backupSource; } - public LayoutType getLayout() { + public String getLayout() { return this.layout; } - public void setLayout(LayoutType layout) { + public void setLayout(String layout) { this.layout = layout; } @@ -276,29 +277,4 @@ public class SpringBootPluginExtension { } } - /** - * Layout Types. - */ - enum LayoutType { - - JAR(new Layouts.Jar()), - - WAR(new Layouts.War()), - - ZIP(new Layouts.Expanded()), - - DIR(new Layouts.Expanded()), - - MODULE(new Layouts.Module()), - - NONE(new Layouts.None()); - - Layout layout; - - LayoutType(Layout layout) { - this.layout = layout; - } - - } - } 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 3eed102ae4..32d137c754 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 @@ -51,8 +51,6 @@ import java.util.zip.ZipEntry; */ public class JarWriter { - private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar"; - private static final int BUFFER_SIZE = 32 * 1024; private final JarOutputStream jarOutput; @@ -203,9 +201,20 @@ public class JarWriter { /** * Write the required spring-boot-loader classes to the JAR. * @throws IOException if the classes cannot be written + * @deprecated us {@link #writeLoaderClasses(String)} instead */ + @Deprecated public void writeLoaderClasses() throws IOException { - URL loaderJar = getClass().getClassLoader().getResource(NESTED_LOADER_JAR); + writeLoaderClasses(Layouts.DEFAULT_LOADER_JAR); + } + + /** + * Write the required spring-boot-loader classes to the JAR. + * @param loaderJarPath the path to the loader jar (in the classpath) + * @throws IOException if the classes cannot be written + */ + public void writeLoaderClasses(String loaderJarPath) throws IOException { + URL loaderJar = getClass().getClassLoader().getResource(loaderJarPath); JarInputStream inputStream = new JarInputStream( new BufferedInputStream(loaderJar.openStream())); JarEntry entry; diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java index 38681e107e..09f7ded6be 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layout.java @@ -40,15 +40,25 @@ public interface Layout { String getLibraryDestination(String libraryName, LibraryScope scope); /** - * Returns the location of classes within the archive. + * Returns the location of classes within the archive. Empty if the location is the + * root path, otherwise ends with a slash ('/'). * @return the classes location */ String getClassesLocation(); /** - * Returns if loader classes should be included to make the archive executable. + * Returns if loader classes should be included to make the archive executable. If + * true, then {@link #getLoaderJarPath()} should point to a valid jar file that + * contains the loader classes. * @return if the layout is executable */ boolean isExecutable(); + /** + * Returns the path to a nested jar that contains the loader, and which will be + * unpacked into the root of the repackaged jar. + * @return the path to a nested jar that contains the loader + */ + String getLoaderJarPath(); + } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutFactory.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutFactory.java new file mode 100644 index 0000000000..9b21eceeb3 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutFactory.java @@ -0,0 +1,31 @@ +/* + * 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.loader.tools; + +/** + * Strategy for creating instances of {@link Layout}. + * + * @author Dave Syer + * + */ +public interface LayoutFactory { + + Layout getLayout(); + + String getName(); + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutType.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutType.java new file mode 100644 index 0000000000..6901a21117 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutType.java @@ -0,0 +1,101 @@ +/* + * 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.loader.tools; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * Archive layout types. + * + * @author Dave Syer + */ +public enum LayoutType { + + /** + * Jar Layout. + */ + JAR(new Layouts.Jar()), + + /** + * War Layout. + */ + WAR(new Layouts.War()), + + /** + * Zip Layout. + */ + ZIP(new Layouts.Expanded()), + + /** + * Dir Layout. + */ + DIR(new Layouts.Expanded()), + + /** + * Module Layout. + */ + MODULE(new Layouts.Module()), + + /** + * No Layout. + */ + NONE(new Layouts.None()); + + private static Map customTypes; + + private final Layout layout; + + public Layout layout() { + return this.layout; + } + + LayoutType(Layout layout) { + this.layout = layout; + } + + public static Layout layout(String value) { + try { + return valueOf(value).layout(); + } + catch (IllegalArgumentException e) { + if (customTypes == null) { + customTypes = new HashMap(); + lookupCustomTypes(); + } + Layout layout = customTypes.get(value); + if (layout == null) { + throw new IllegalArgumentException( + "Cannot resolve custom layout type: " + value); + } + return layout; + } + } + + private static void lookupCustomTypes() { + ClassLoader classLoader = LayoutType.class.getClassLoader(); + List factories = SpringFactoriesLoader + .loadFactories(LayoutFactory.class, classLoader); + for (LayoutFactory factory : factories) { + customTypes.put(factory.getName(), factory.getLayout()); + } + } + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java index 3cebdc7a05..992a6ab40e 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java @@ -33,6 +33,11 @@ import java.util.Set; */ public final class Layouts { + /** + * Default value for {@link #getLoaderJarPath()}. + */ + public static final String DEFAULT_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar"; + private Layouts() { } @@ -87,6 +92,11 @@ public final class Layouts { return true; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } /** @@ -116,6 +126,11 @@ public final class Layouts { return false; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } /** @@ -154,6 +169,11 @@ public final class Layouts { return true; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } /** @@ -188,6 +208,11 @@ public final class Layouts { return false; } + @Override + public String getLoaderJarPath() { + return DEFAULT_LOADER_JAR; + } + } } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index fb44ec1a93..7e8564b62b 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -216,7 +216,7 @@ public class Repackager { } writeNestedLibraries(standardLibraries, seen, writer); if (this.layout.isExecutable()) { - writer.writeLoaderClasses(); + writer.writeLoaderClasses(this.layout.getLoaderJarPath()); } } finally { diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/LayoutTypeTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/LayoutTypeTests.java new file mode 100644 index 0000000000..3792529f22 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/LayoutTypeTests.java @@ -0,0 +1,53 @@ +/* + * 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.loader.tools; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dave Syer + * + */ +public class LayoutTypeTests { + + @Test + public void standardType() { + assertThat(LayoutType.layout("DIR")) + .isEqualTo(LayoutType.valueOf("DIR").layout()); + } + + @Test + public void customType() { + assertThat(LayoutType.layout("CUSTOM")).isNotNull(); + } + + public static class TestLayoutFactory implements LayoutFactory { + + @Override + public Layout getLayout() { + return new Layouts.Jar(); + } + + @Override + public String getName() { + return "CUSTOM"; + } + } + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/resources/META-INF/spring.factories b/spring-boot-tools/spring-boot-loader-tools/src/test/resources/META-INF/spring.factories new file mode 100644 index 0000000000..37eac70aa3 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.loader.tools.LayoutFactory=\ +org.springframework.boot.loader.tools.LayoutTypeTests.TestLayoutFactory \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java index 309eb72db3..42de1700b4 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java @@ -42,8 +42,7 @@ import org.apache.maven.shared.artifact.filter.collection.ScopeFilter; import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.LaunchScript; -import org.springframework.boot.loader.tools.Layout; -import org.springframework.boot.loader.tools.Layouts; +import org.springframework.boot.loader.tools.LayoutType; import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Repackager; @@ -131,7 +130,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { * @since 1.0 */ @Parameter - private LayoutType layout; + private String layout; /** * A list of the libraries that must be unpacked from fat jars in order to run. @@ -228,7 +227,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { repackager.setMainClass(this.mainClass); if (this.layout != null) { getLog().info("Layout: " + this.layout); - repackager.setLayout(this.layout.layout()); + repackager.setLayout(LayoutType.layout(this.layout)); } return repackager; } @@ -309,53 +308,6 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { } } - /** - * Archive layout types. - */ - public enum LayoutType { - - /** - * Jar Layout. - */ - JAR(new Layouts.Jar()), - - /** - * War Layout. - */ - WAR(new Layouts.War()), - - /** - * Zip Layout. - */ - ZIP(new Layouts.Expanded()), - - /** - * Dir Layout. - */ - DIR(new Layouts.Expanded()), - - /** - * Module Layout. - */ - MODULE(new Layouts.Module()), - - /** - * No Layout. - */ - NONE(new Layouts.None()); - - private final Layout layout; - - public Layout layout() { - return this.layout; - } - - LayoutType(Layout layout) { - this.layout = layout; - } - - } - private static class LoggingRepackager extends Repackager { private final Log log; diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt.vm b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt.vm new file mode 100644 index 0000000000..031e29ff71 --- /dev/null +++ b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt.vm @@ -0,0 +1,57 @@ + ----- + Use a custom layout + ----- + Dave Syer + ----- + 2016-10-30 + ----- + + Spring Boot repackages the jar file for this project using a custom + layout defined in the additional jar file, provided as a dependency + to the build plugin: + +--- + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + + repackage + + + CUSTOM + + + + + + com.example + custom-layout + 0.0.1.BUILD-SNAPSHOT + + + ... + + ... + + ... + + ... + +--- + + The layout is provided as an implementation of <> + (from spring-boot-loader-tools) listed in + <> inside the <> jar. + + + + diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt index dcf5084405..cb767f9e64 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt +++ b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt @@ -48,6 +48,8 @@ Spring Boot Maven Plugin * {{{./examples/repackage-disable-attach.html}Local repackaged artifact}} + * {{{./examples/custom-layout.html}Custom layout}} + * {{{./examples/exclude-dependency.html}Exclude a dependency}} * {{{./examples/run-debug.html}Debug the application}}