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..aea50dc776 100644 --- a/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc +++ b/spring-boot-docs/src/main/asciidoc/build-tool-plugins.adoc @@ -484,6 +484,11 @@ The following configuration options are available: (defaults to a guess based on the archive type). See <>. +|'layoutFactory` +|A layout factory that can be used if a custom layout is required. Alternative layouts +can be provided by 3rd parties. Layout factories are only used when `layout` is not +specified. + |`requiresUnpack` |A list of dependencies (in the form "`groupId:artifactId`" that must be unpacked from fat jars in order to run. Items are still packaged into the fat jar, but they will be @@ -530,6 +535,38 @@ loader should be included or not. The following layouts are available: ++[[build-tool-plugins-gradle-configuration-custom-repackager]] ++==== 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. Any library which defines one +or more `LayoutFactory` implementations can be added to the build script dependencies +and then the layout factory becomes available in the `springBoot` configuration. +For example: + +[source,groovy,indent=0,subs="verbatim,attributes"] +---- +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-version}") + classpath("com.example:custom-layout:1.0.0") + } +} + +springBoot { + layoutFactory = new com.example.CustomLayoutFactory() +} ++---- + +NOTE: If there is only one custom `LayoutFactory` on the build classpath and it is +listed in `META-INF/spring.factories` then it is unnecessary to explicitly set it in the +`springBoot` configuration. Layout factories are only used when no explicit `layout` is +specified. + + + [[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-samples/pom.xml b/spring-boot-samples/pom.xml index c9f0c79b64..ef695c4313 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -32,6 +32,7 @@ spring-boot-sample-atmosphere spring-boot-sample-batch spring-boot-sample-cache + spring-boot-sample-custom-layout spring-boot-sample-data-cassandra spring-boot-sample-data-couchbase spring-boot-sample-data-elasticsearch diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/pom.xml b/spring-boot-samples/spring-boot-sample-custom-layout/pom.xml new file mode 100644 index 0000000000..2bf7677aa6 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.5.0.BUILD-SNAPSHOT + + spring-boot-sample-custom-layout + Spring Boot Custom Layout Sample + Spring Boot Custom Layout Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-loader-tools + + + org.springframework.boot + spring-boot-starter-test + test + + + org.gradle + gradle-tooling-api + ${gradle.version} + test + + + + + + org.apache.maven.plugins + maven-invoker-plugin + + ${project.build.directory}/it + src/it/settings.xml + ${project.build.directory}/local-repo + verify + true + ${skipTests} + true + + + + integration-test + + install + run + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-effective-pom + generate-test-resources + + copy + + + + + org.springframework.boot + spring-boot-dependencies + ${project.version} + effective-pom + true + ${project.build.directory} + dependencies-pom.xml + + + + + + + + + + + gradle + http://repo.gradle.org/gradle/libs-releases-local + + true + + + false + + + + diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/build.gradle b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/build.gradle new file mode 100644 index 0000000000..4bad1c3da1 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/build.gradle @@ -0,0 +1,28 @@ +buildscript { + repositories { + flatDir { + dirs '../..' + } + mavenLocal() + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.bootVersion}" + classpath "org.springframework.boot:spring-boot-sample-custom-layout:${project.bootVersion}" + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' + +springBoot { + layoutFactory = new sample.layout.SampleLayoutFactory('custom') +} + +dependencies { + compile 'org.springframework.boot:spring-boot-starter' +} diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/pom.xml b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/pom.xml new file mode 100644 index 0000000000..c8e41f877d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + org.springframework.boot.maven.it + custom + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + + + + + org.springframework.boot + spring-boot-maven-plugin + @project.version@ + + + + repackage + + + + custom + + + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + + + + + diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/src/main/java/org/test/SampleApplication.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/src/main/java/org/test/SampleApplication.java new file mode 100644 index 0000000000..e8784d4593 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,24 @@ +/* + * 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.test; + +public class SampleApplication { + + public static void main(String[] args) { + } + +} diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/verify.groovy b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/verify.groovy new file mode 100644 index 0000000000..bcb4277c0e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/custom/verify.groovy @@ -0,0 +1,7 @@ +import java.io.*; +import sample.layout.*; + +Verify.verify( + new File( basedir, "target/custom-0.0.1.BUILD-SNAPSHOT.jar" ), "custom" +); + diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/build.gradle b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/build.gradle new file mode 100644 index 0000000000..7c10b209ff --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/build.gradle @@ -0,0 +1,24 @@ +buildscript { + repositories { + flatDir { + dirs '../..' + } + mavenLocal() + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.bootVersion}" + classpath "org.springframework.boot:spring-boot-sample-custom-layout:${project.bootVersion}" + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' + +dependencies { + compile 'org.springframework.boot:spring-boot-starter' +} diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/pom.xml b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/pom.xml new file mode 100644 index 0000000000..8f5ef946cb --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + org.springframework.boot.maven.it + default + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + + + + + org.springframework.boot + spring-boot-maven-plugin + @project.version@ + + + + repackage + + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + + + + + diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/src/main/java/org/test/SampleApplication.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/src/main/java/org/test/SampleApplication.java new file mode 100644 index 0000000000..e8784d4593 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,24 @@ +/* + * 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.test; + +public class SampleApplication { + + public static void main(String[] args) { + } + +} diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/verify.groovy b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/verify.groovy new file mode 100644 index 0000000000..a9cd8afb07 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/it/default/verify.groovy @@ -0,0 +1,7 @@ +import java.io.*; +import sample.layout.*; + +Verify.verify( + new File( basedir, "target/default-0.0.1.BUILD-SNAPSHOT.jar" ), "sample" +); + diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayout.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayout.java new file mode 100644 index 0000000000..ce993bf9b4 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayout.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2016 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 sample.layout; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.springframework.boot.loader.tools.CustomLoaderLayout; +import org.springframework.boot.loader.tools.Layouts; +import org.springframework.boot.loader.tools.LoaderClassesWriter; + +/** + * @author pwebb + */ +public class SampleLayout extends Layouts.Jar implements CustomLoaderLayout { + + private String name; + + public SampleLayout(String name) { + this.name = name; + } + + @Override + public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { + writer.writeEntry(this.name, new ByteArrayInputStream("test".getBytes())); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayoutFactory.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayoutFactory.java new file mode 100644 index 0000000000..50a7ff1f4d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/java/sample/layout/SampleLayoutFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2016 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 sample.layout; + +import java.io.File; + +import org.springframework.boot.loader.tools.Layout; +import org.springframework.boot.loader.tools.LayoutFactory; + +public class SampleLayoutFactory implements LayoutFactory { + + private String name = "sample"; + + public SampleLayoutFactory() { + } + + public SampleLayoutFactory(String name) { + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public Layout getLayout(File source) { + return new SampleLayout(this.name); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/main/resources/META-INF/spring.factories b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..3f2ec6985e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.loader.tools.LayoutFactory=\ +sample.layout.SampleLayoutFactory diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/GradeIT.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/GradeIT.java new file mode 100644 index 0000000000..5a132fa00e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/GradeIT.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2016 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 sample.layout; + +import java.io.File; +import java.io.FileReader; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathFactory; + +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; +import org.gradle.tooling.internal.consumer.DefaultGradleConnector; +import org.junit.Test; +import org.xml.sax.InputSource; + +import org.springframework.util.FileCopyUtils; + +public class GradeIT { + + @Test + public void sampleDefault() throws Exception { + test("default", "sample"); + } + + @Test + public void sampleCustom() throws Exception { + test("custom", "custom"); + } + + private void test(String name, String expected) throws Exception { + File projectDirectory = new File("target/gradleit/" + name); + File javaDirectory = new File( + "target/gradleit/" + name + "/src/main/java/org/test/"); + projectDirectory.mkdirs(); + javaDirectory.mkdirs(); + File script = new File(projectDirectory, "build.gradle"); + FileCopyUtils.copy(new File("src/it/" + name + "/build.gradle"), script); + FileCopyUtils.copy( + new File("src/it/" + name + + "/src/main/java/org/test/SampleApplication.java"), + new File(javaDirectory, "SampleApplication.java")); + GradleConnector gradleConnector = GradleConnector.newConnector(); + gradleConnector.useGradleVersion("2.9"); + ((DefaultGradleConnector) gradleConnector).embedded(true); + ProjectConnection project = gradleConnector.forProjectDirectory(projectDirectory) + .connect(); + project.newBuild().forTasks("clean", "build") + .withArguments("-PbootVersion=" + getBootVersion()).run(); + Verify.verify( + new File("target/gradleit/" + name + "/build/libs/" + name + ".jar"), + expected); + } + + public static String getBootVersion() { + return evaluateExpression( + "/*[local-name()='project']/*[local-name()='version']" + "/text()"); + } + + private static String evaluateExpression(String expression) { + try { + XPathFactory xPathFactory = XPathFactory.newInstance(); + XPath xpath = xPathFactory.newXPath(); + XPathExpression expr = xpath.compile(expression); + String version = expr.evaluate( + new InputSource(new FileReader("target/dependencies-pom.xml"))); + return version; + } + catch (Exception ex) { + throw new IllegalStateException("Failed to evaluate expression", ex); + } + } + +} diff --git a/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/Verify.java b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/Verify.java new file mode 100644 index 0000000000..496eea6728 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-custom-layout/src/test/java/sample/layout/Verify.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2016 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 sample.layout; + +import java.io.File; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public final class Verify { + + private Verify() { + } + + public static void verify(File file, String entry) throws Exception { + ZipFile zipFile = new ZipFile(file); + try { + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + if (entries.nextElement().getName().equals(entry)) { + return; + } + } + throw new AssertionError("No entry " + entry); + } + finally { + zipFile.close(); + } + } + +} 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..3ddd57c5a5 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 @@ -26,6 +26,7 @@ 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.LayoutFactory; import org.springframework.boot.loader.tools.Layouts; /** @@ -90,6 +91,12 @@ public class SpringBootPluginExtension { */ LayoutType layout; + /** + * The layout factory that will be used when no explicit layout is specified. + * Alternative layouts can be provided by 3rd parties. + */ + LayoutFactory layoutFactory; + /** * Libraries that must be unpacked from fat jars in order to run. Use Strings in the * form {@literal groupId:artifactId}. @@ -196,6 +203,14 @@ public class SpringBootPluginExtension { this.layout = layout; } + public LayoutFactory getLayoutFactory() { + return this.layoutFactory; + } + + public void setLayoutFactory(LayoutFactory layoutFactory) { + this.layoutFactory = layoutFactory; + } + public Set getRequiresUnpack() { return this.requiresUnpack; } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java index 92d99dbf35..4e78f2478b 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/repackage/RepackageTask.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; import org.gradle.api.Action; import org.gradle.api.DefaultTask; @@ -35,6 +34,7 @@ import org.springframework.boot.gradle.SpringBootPluginExtension; import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.Repackager; +import org.springframework.boot.loader.tools.Repackager.MainClassTimeoutWarningListener; import org.springframework.util.FileCopyUtils; /** @@ -46,8 +46,6 @@ import org.springframework.util.FileCopyUtils; */ public class RepackageTask extends DefaultTask { - private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10); - private String customConfiguration; private Object withJarTask; @@ -215,7 +213,10 @@ public class RepackageTask extends DefaultTask { copy(file, outputFile); file = outputFile; } - Repackager repackager = new LoggingRepackager(file); + Repackager repackager = new Repackager(file, + this.extension.getLayoutFactory()); + repackager.addMainClassTimeoutWarningListener( + new LoggingMainClassTimeoutWarningListener()); setMainClass(repackager); if (this.extension.convertLayout() != null) { repackager.setLayout(this.extension.convertLayout()); @@ -305,26 +306,13 @@ public class RepackageTask extends DefaultTask { /** * {@link Repackager} that also logs when searching takes too long. */ - private class LoggingRepackager extends Repackager { - - LoggingRepackager(File source) { - super(source); - } + private class LoggingMainClassTimeoutWarningListener + implements MainClassTimeoutWarningListener { @Override - protected String findMainMethod(java.util.jar.JarFile source) throws IOException { - long startTime = System.currentTimeMillis(); - try { - return super.findMainMethod(source); - } - finally { - long duration = System.currentTimeMillis() - startTime; - if (duration > FIND_WARNING_TIMEOUT) { - getLogger().warn("Searching for the main-class is taking " - + "some time, consider using setting " - + "'springBoot.mainClass'"); - } - } + public void handleTimeoutWarning(long duration, String mainMethod) { + getLogger().warn("Searching for the main-class is taking " + + "some time, consider using setting " + "'springBoot.mainClass'"); } } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/CustomLoaderLayout.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/CustomLoaderLayout.java new file mode 100644 index 0000000000..746a12fd3a --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/CustomLoaderLayout.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2016 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.io.IOException; + +/** + * Additional interface that can be implemented by {@link Layout Layouts} that write their + * own loader classes. + * + * @author Phillip Webb + * @since 1.5.0 + */ +public interface CustomLoaderLayout { + + /** + * Write the required loader classes into the JAR. + * @param writer the writer used to write the classes + * @throws IOException if the classes cannot be written + */ + void writeLoadedClasses(LoaderClassesWriter writer) throws IOException; + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/DefaultLayoutFactory.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/DefaultLayoutFactory.java new file mode 100644 index 0000000000..b9d196dbed --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/DefaultLayoutFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2016 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.io.File; + +/** + * Default implementation of {@link LayoutFactory}. + * + * @author Phillip Webb + * @since 1.5.0 + */ +public class DefaultLayoutFactory implements LayoutFactory { + + @Override + public Layout getLayout(File source) { + return Layouts.forFile(source); + } + +} 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..4d461c6e33 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 @@ -49,7 +49,7 @@ import java.util.zip.ZipEntry; * @author Phillip Webb * @author Andy Wilkinson */ -public class JarWriter { +public class JarWriter implements LoaderClassesWriter { private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar"; @@ -155,6 +155,7 @@ public class JarWriter { * @param inputStream The stream from which the entry's data can be read * @throws IOException if the write fails */ + @Override public void writeEntry(String entryName, InputStream inputStream) throws IOException { JarEntry entry = new JarEntry(entryName); writeEntry(entry, new InputStreamEntryWriter(inputStream, true)); @@ -204,8 +205,20 @@ public class JarWriter { * Write the required spring-boot-loader classes to the JAR. * @throws IOException if the classes cannot be written */ + @Override public void writeLoaderClasses() throws IOException { - URL loaderJar = getClass().getClassLoader().getResource(NESTED_LOADER_JAR); + writeLoaderClasses(NESTED_LOADER_JAR); + } + + /** + * Write the required spring-boot-loader classes to the JAR. + * @param loaderJarResourceName the name of the resource containing the loader classes + * to be written + * @throws IOException if the classes cannot be written + */ + @Override + public void writeLoaderClasses(String loaderJarResourceName) throws IOException { + URL loaderJar = getClass().getClassLoader().getResource(loaderJarResourceName); 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..5c13528fb9 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 @@ -18,9 +18,13 @@ package org.springframework.boot.loader.tools; /** * Strategy interface used to determine the layout for a particular type of archive. + * Layouts may additionally implement {@link CustomLoaderLayout} if they wish to write + * custom loader classes. * * @author Phillip Webb * @see Layouts + * @see RepackagingLayout + * @see CustomLoaderLayout */ public interface Layout { 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..0a6e7eeb33 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LayoutFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2016 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.io.File; + +/** + * Factory interface used to create a {@link Layout}. + * + * @author Dave Syer + * @author Phillip Webb + */ +public interface LayoutFactory { + + /** + * Return a {@link Layout} for the specified source file. + * @param source the source file + * @return the layout to use for the file + */ + Layout getLayout(File source); + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java new file mode 100644 index 0000000000..6e2324de8e --- /dev/null +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2016 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.io.IOException; +import java.io.InputStream; + +/** + * Writer used by {@link CustomLoaderLayout CustomLoaderLayouts} to write classes into a + * repackaged JAR. + * + * @author Phillip Webb + * @since 1.5.0 + */ +public interface LoaderClassesWriter { + + /** + * Write the default required spring-boot-loader classes to the JAR. + * @throws IOException if the classes cannot be written + */ + void writeLoaderClasses() throws IOException; + + /** + * Write custom required spring-boot-loader classes to the JAR. + * @param loaderJarResourceName the name of the resource containing the loader classes + * to be written + * @throws IOException if the classes cannot be written + */ + void writeLoaderClasses(String loaderJarResourceName) throws IOException; + + /** + * Write a single entry to the JAR. + * @param name the name of the entry + * @param inputStream the input stream content + * @throws IOException if the entry cannot be written + */ + void writeEntry(String name, InputStream inputStream) throws IOException; + +} 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..aca3f5fbaf 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 @@ -24,11 +24,15 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.springframework.boot.loader.tools.JarWriter.EntryTransformer; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Utility class that can be used to repackage an archive so that it can be executed using @@ -52,6 +56,10 @@ public class Repackager { private static final byte[] ZIP_FILE_HEADER = new byte[] { 'P', 'K', 3, 4 }; + private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + + private List mainClassTimeoutListeners = new ArrayList(); + private String mainClass; private boolean backupSource = true; @@ -60,12 +68,28 @@ public class Repackager { private Layout layout; + private LayoutFactory layoutFactory; + public Repackager(File source) { + this(source, null); + } + + public Repackager(File source, LayoutFactory layoutFactory) { if (source == null || !source.exists() || !source.isFile()) { throw new IllegalArgumentException("Source must refer to an existing file"); } this.source = source.getAbsoluteFile(); - this.layout = Layouts.forFile(source); + this.layoutFactory = layoutFactory; + } + + /** + * Add a listener that will be triggered to dispaly a warning if searching for the + * main class takes too long. + * @param listener the listener to add + */ + public void addMainClassTimeoutWarningListener( + MainClassTimeoutWarningListener listener) { + this.mainClassTimeoutListeners.add(listener); } /** @@ -97,6 +121,15 @@ public class Repackager { this.layout = layout; } + /** + * Sets the layout factory for the jar. The factory can be used when no specific + * layout is specific. + * @param layoutFactory the layoutFactory to set + */ + public void setLayoutFactory(LayoutFactory layoutFactory) { + this.layoutFactory = layoutFactory; + } + /** * Repackage the source file so that it can be run using '{@literal java -jar}'. * @param libraries the libraries required to run the archive @@ -134,6 +167,9 @@ public class Repackager { if (libraries == null) { throw new IllegalArgumentException("Libraries must not be null"); } + if (this.layout == null) { + this.layout = getLayoutFactory().getLayout(this.source); + } if (alreadyRepackaged()) { return; } @@ -161,6 +197,19 @@ public class Repackager { } } + private LayoutFactory getLayoutFactory() { + if (this.layoutFactory != null) { + return this.layoutFactory; + } + List factories = SpringFactoriesLoader + .loadFactories(LayoutFactory.class, null); + if (factories.isEmpty()) { + return new DefaultLayoutFactory(); + } + Assert.state(factories.size() == 1, "No unique LayoutFactory found"); + return factories.get(0); + } + /** * Return the {@link File} to use to backup the original source. * @return the file to use to backup the original source @@ -203,21 +252,7 @@ public class Repackager { } }); - writer.writeManifest(buildManifest(sourceJar)); - Set seen = new HashSet(); - writeNestedLibraries(unpackLibraries, seen, writer); - if (this.layout instanceof RepackagingLayout) { - writer.writeEntries(sourceJar, - new RenamingEntryTransformer(((RepackagingLayout) this.layout) - .getRepackagedClassesLocation())); - } - else { - writer.writeEntries(sourceJar); - } - writeNestedLibraries(standardLibraries, seen, writer); - if (this.layout.isExecutable()) { - writer.writeLoaderClasses(); - } + repackage(sourceJar, writer, unpackLibraries, standardLibraries); } finally { try { @@ -229,6 +264,23 @@ public class Repackager { } } + private void repackage(JarFile sourceJar, JarWriter writer, + final List unpackLibraries, final List standardLibraries) + throws IOException { + writer.writeManifest(buildManifest(sourceJar)); + Set seen = new HashSet(); + writeNestedLibraries(unpackLibraries, seen, writer); + if (this.layout instanceof RepackagingLayout) { + writer.writeEntries(sourceJar, new RenamingEntryTransformer( + ((RepackagingLayout) this.layout).getRepackagedClassesLocation())); + } + else { + writer.writeEntries(sourceJar); + } + writeNestedLibraries(standardLibraries, seen, writer); + writeLoaderClasses(writer); + } + private void writeNestedLibraries(List libraries, Set alreadySeen, JarWriter writer) throws IOException { for (Library library : libraries) { @@ -244,6 +296,15 @@ public class Repackager { } } + private void writeLoaderClasses(JarWriter writer) throws IOException { + if (this.layout instanceof CustomLoaderLayout) { + ((CustomLoaderLayout) this.layout).writeLoadedClasses(writer); + } + else if (this.layout.isExecutable()) { + writer.writeLoaderClasses(); + } + } + private boolean isZip(File file) { try { FileInputStream fileInputStream = new FileInputStream(file); @@ -280,7 +341,7 @@ public class Repackager { startClass = manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE); } if (startClass == null) { - startClass = findMainMethod(source); + startClass = findMainMethodWithTimeoutWarning(source); } String launcherClassName = this.layout.getLauncherClassName(); if (launcherClassName != null) { @@ -300,11 +361,25 @@ public class Repackager { (this.layout instanceof RepackagingLayout) ? ((RepackagingLayout) this.layout).getRepackagedClassesLocation() : this.layout.getClassesLocation()); - manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, - this.layout.getLibraryDestination("", LibraryScope.COMPILE)); + String lib = this.layout.getLibraryDestination("", LibraryScope.COMPILE); + if (StringUtils.hasLength(lib)) { + manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, lib); + } return manifest; } + private String findMainMethodWithTimeoutWarning(JarFile source) throws IOException { + long startTime = System.currentTimeMillis(); + String mainMethod = findMainMethod(source); + long duration = System.currentTimeMillis() - startTime; + if (duration > FIND_WARNING_TIMEOUT) { + for (MainClassTimeoutWarningListener listener : this.mainClassTimeoutListeners) { + listener.handleTimeoutWarning(duration, mainMethod); + } + } + return mainMethod; + } + protected String findMainMethod(JarFile source) throws IOException { return MainClassFinder.findSingleMainClass(source, this.layout.getClassesLocation()); @@ -323,6 +398,21 @@ public class Repackager { } } + /** + * Callback interface used to present a warning when finding the main class takes too + * long. + */ + public interface MainClassTimeoutWarningListener { + + /** + * Handle a timeout warning. + * @param duration the amount of time it took to find the main method + * @param mainMethod the main method that was actually found + */ + void handleTimeoutWarning(long duration, String mainMethod); + + } + /** * An {@code EntryTransformer} that renames entries by applying a prefix. */ diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java index 4c3ee88ea6..beb8203eb5 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.loader.tools; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -347,14 +348,46 @@ public class RepackagerTests { final LibraryScope scope = mock(LibraryScope.class); given(layout.getLauncherClassName()).willReturn("testLauncher"); given(layout.getLibraryDestination(anyString(), eq(scope))).willReturn("test/"); + given(layout.getLibraryDestination(anyString(), eq(LibraryScope.COMPILE))) + .willReturn("test-lib/"); repackager.setLayout(layout); repackager.repackage(new Libraries() { + @Override public void doWithLibraries(LibraryCallback callback) throws IOException { callback.library(new Library(libJarFile, scope)); } + }); assertThat(hasEntry(file, "test/" + libJarFile.getName())).isTrue(); + assertThat(getManifest(file).getMainAttributes().getValue("Spring-Boot-Lib")) + .isEqualTo("test-lib/"); + assertThat(getManifest(file).getMainAttributes().getValue("Main-Class")) + .isEqualTo("testLauncher"); + } + + @Test + public void customLayoutNoBootLib() throws Exception { + TestJarFile libJar = new TestJarFile(this.temporaryFolder); + libJar.addClass("a/b/C.class", ClassWithoutMainMethod.class); + final File libJarFile = libJar.getFile(); + this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class); + File file = this.testJarFile.getFile(); + Repackager repackager = new Repackager(file); + Layout layout = mock(Layout.class); + final LibraryScope scope = mock(LibraryScope.class); + given(layout.getLauncherClassName()).willReturn("testLauncher"); + repackager.setLayout(layout); + repackager.repackage(new Libraries() { + + @Override + public void doWithLibraries(LibraryCallback callback) throws IOException { + callback.library(new Library(libJarFile, scope)); + } + + }); + assertThat(getManifest(file).getMainAttributes().getValue("Spring-Boot-Lib")) + .isNull(); assertThat(getManifest(file).getMainAttributes().getValue("Main-Class")) .isEqualTo("testLauncher"); } @@ -536,6 +569,29 @@ public class RepackagerTests { } } + @Test + public void customLayoutFactoryWithoutLayout() throws Exception { + this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class); + File source = this.testJarFile.getFile(); + Repackager repackager = new Repackager(source, new TestLayoutFactory()); + repackager.repackage(NO_LIBRARIES); + JarFile jarFile = new JarFile(source); + assertThat(jarFile.getEntry("test")).isNotNull(); + jarFile.close(); + } + + @Test + public void customLayoutFactoryWithLayout() throws Exception { + this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class); + File source = this.testJarFile.getFile(); + Repackager repackager = new Repackager(source, new TestLayoutFactory()); + repackager.setLayout(new Layouts.Jar()); + repackager.repackage(NO_LIBRARIES); + JarFile jarFile = new JarFile(source); + assertThat(jarFile.getEntry("test")).isNull(); + jarFile.close(); + } + private boolean hasLauncherClasses(File file) throws IOException { return hasEntry(file, "org/springframework/boot/") && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class"); @@ -580,4 +636,22 @@ public class RepackagerTests { } + public static class TestLayoutFactory implements LayoutFactory { + + @Override + public Layout getLayout(File source) { + return new TestLayout(); + } + + } + + private static class TestLayout extends Layouts.Jar implements CustomLoaderLayout { + + @Override + public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { + writer.writeEntry("test", new ByteArrayInputStream("test".getBytes())); + } + + } + } 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..7569d02142 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 @@ -22,14 +22,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.jar.JarFile; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; @@ -43,9 +40,11 @@ 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.LayoutFactory; import org.springframework.boot.loader.tools.Layouts; import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Repackager; +import org.springframework.boot.loader.tools.Repackager.MainClassTimeoutWarningListener; /** * Repackages existing JAR and WAR archives so that they can be executed from the command @@ -59,8 +58,6 @@ import org.springframework.boot.loader.tools.Repackager; @Mojo(name = "repackage", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class RepackageMojo extends AbstractDependencyFilterMojo { - private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10); - /** * The Maven project. * @since 1.0 @@ -133,6 +130,15 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { @Parameter private LayoutType layout; + /** + * The layout factory that will be used to create the executable archive if no + * explicit layout is set. Alternative layouts implementations can be provided by 3rd + * parties. + * @since 1.5 + */ + @Parameter + private LayoutFactory layoutFactory; + /** * A list of the libraries that must be unpacked from fat jars in order to run. * Specify each library as a <dependency> with a @@ -224,7 +230,9 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { } private Repackager getRepackager(File source) { - Repackager repackager = new LoggingRepackager(source, getLog()); + Repackager repackager = new Repackager(source, this.layoutFactory); + repackager.addMainClassTimeoutWarningListener( + new LoggingMainClassTimeoutWarningListener()); repackager.setMainClass(this.mainClass); if (this.layout != null) { getLog().info("Layout: " + this.layout); @@ -356,30 +364,15 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { } - private static class LoggingRepackager extends Repackager { - - private final Log log; - - LoggingRepackager(File source, Log log) { - super(source); - this.log = log; - } + private class LoggingMainClassTimeoutWarningListener + implements MainClassTimeoutWarningListener { @Override - protected String findMainMethod(JarFile source) throws IOException { - long startTime = System.currentTimeMillis(); - try { - return super.findMainMethod(source); - } - finally { - long duration = System.currentTimeMillis() - startTime; - if (duration > FIND_WARNING_TIMEOUT) { - this.log.warn("Searching for the main-class is taking some time, " - + "consider using the mainClass configuration " - + "parameter"); - } - } + public void handleTimeoutWarning(long duration, String mainMethod) { + getLog().warn("Searching for the main-class is taking some time, " + + "consider using the mainClass configuration " + "parameter"); } } + } diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt new file mode 100644 index 0000000000..f4b5809286 --- /dev/null +++ b/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/custom-layout.apt @@ -0,0 +1,58 @@ + ----- + Use a custom layout + ----- + Dave Syer + ----- + 2016-10-30 + ----- + + Spring Boot repackages the jar file for this project using a custom layout factory + defined in the additional jar file, provided as a dependency to the build plugin: + +--- + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + + repackage + + + + value + + + + + + + com.example + custom-layout + 0.0.1.BUILD-SNAPSHOT + + + ... + + ... + + ... + + ... + ++--- + + The layout factory is provided as an implementation of <<>> (from + spring-boot-loader-tools) explicitly specified in the pom. If there is only one custom + <<>> on the plugin classpath and it is listed in + <<>> then it is unnecessary to explicitly set it in the + plugin configuration. + + Layout factories are always ignored if an explicit <> is set. 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 033d484e26..fca3d4f6f7 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}}