diff --git a/examples/.idea/vcs.xml b/examples/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/examples/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java b/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java index efb10a7..a141f1c 100644 --- a/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java +++ b/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java @@ -3,6 +3,7 @@ package com.example.demo; import rife.bld.BuildCommand; import rife.bld.WebProject; import rife.bld.extension.BootJarOperation; +import rife.bld.extension.BootWarOperation; import java.util.List; import java.util.logging.ConsoleHandler; @@ -49,9 +50,21 @@ public class DemoApplicationBuild extends WebProject { } @BuildCommand(summary = "Creates an executable JAR for the project") - public void jar() throws Exception { + public void bootjar() throws Exception { new BootJarOperation() .fromProject(this) .execute(); } + + @BuildCommand(summary = "Creates an executable JAR for the project") + public void uberjar() throws Exception { + bootjar(); + } + + @BuildCommand(summary = "Creates WAR for the project") + public void war() throws Exception { + new BootWarOperation() + .fromProject(this) + .execute(); + } } \ No newline at end of file diff --git a/src/main/java/rife/bld/extension/AbstractBootOperation.java b/src/main/java/rife/bld/extension/AbstractBootOperation.java new file mode 100644 index 0000000..f0ecfe9 --- /dev/null +++ b/src/main/java/rife/bld/extension/AbstractBootOperation.java @@ -0,0 +1,271 @@ +/* + * Copyright 2023 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 + * + * https://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 rife.bld.extension; + +import rife.bld.Project; +import rife.bld.operations.AbstractOperation; +import rife.tools.FileUtils; +import rife.tools.exceptions.FileUtilsErrorException; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.spi.ToolProvider; + +/** + * Implements commons methods used by Spring Boot operations, such as {@link BootJarOperation} and + * {@link BootWarOperation}. + * + * @author Erik C. Thauvin + * @since 1.0 + */ +public abstract class AbstractBootOperation extends AbstractOperation { + private final List manifestAttributes_ = new ArrayList<>(); + private final List sourceDirectories_ = new ArrayList<>(); + protected + Project project_; + private String destinationArchiveFileName; + private File destinationDirectory_; + private String launcherClass_; + + public void deleteDirectories(File... directory) throws FileUtilsErrorException { + for (var d : directory) { + if (d.exists()) { + FileUtils.deleteDirectory(d); + } + } + } + + /** + * Provides the destination file name that will be used for the archive creation. + * + * @param name the war archive destination file name + * @return this operation instance + */ + public AbstractBootOperation destinationArchiveFileName(String name) { + destinationArchiveFileName = name; + return this; + } + + /** + * Retrieves the destination file name that will be used for the JAR creation. + * + * @return the war Jar's destination file name + */ + public String destinationArchiveFileName() { + return destinationArchiveFileName; + } + + /** + * Retrieves the destination directory in which the JAR will be created. + * + * @return the JAR's destination directory + */ + public File destinationDirectory() { + return destinationDirectory_; + } + + /** + * Provides the destination directory in which the archive will be created. + * + * @param directory the war destination directory + * @return this operation instance + * @since 1.5 + */ + public AbstractBootOperation destinationDirectory(File directory) throws IOException { + destinationDirectory_ = directory; + mkDirs(destinationDirectory_); + return this; + } + + /** + * Part of the {@link #execute} operation, copy the {@code BOOT-INF} classes. + */ + protected void executeCopyBootInfClassesFiles(File stagingBootInfDirectory) throws IOException { + var boot_inf_classes_dir = new File(stagingBootInfDirectory, "classes"); + mkDirs(boot_inf_classes_dir); + + for (var dir : sourceDirectories_) { + FileUtils.copyDirectory(dir, boot_inf_classes_dir); + } + + deleteDirectories(new File(boot_inf_classes_dir, "resources"), new File(boot_inf_classes_dir, "templates")); + } + + /** + * Part of the {@link #execute} operation, copy the {@code spring-boot-loader} archive content to the staging directory. + */ + protected void executeCopyBootLoader(File stagingDirectory) throws FileUtilsErrorException { + if (project_.standaloneClasspathJars().isEmpty()) { + throw new IllegalArgumentException("ERROR: Spring Boot Loader required."); + } else { + var meta_inf_dir = new File(stagingDirectory, "META-INF"); + for (var jar : project_.standaloneClasspathJars()) { + FileUtils.unzipFile(jar, stagingDirectory); + if (meta_inf_dir.exists()) { + FileUtils.deleteDirectory(meta_inf_dir); + } + } + } + } + + /** + * Part of the {@link #execute} operation, create the archive from the staging directory. + */ + protected void executeCreateArchive(File stagingDirectory, Logger logger) + throws IOException { + executeCreateManifest(stagingDirectory); + if (logger.isLoggable(Level.FINE) && (!silent())) { + logger.fine(MessageFormat.format("Staging Directory: {0} (exists:{1})", stagingDirectory, + stagingDirectory.exists())); + logger.fine(MessageFormat.format("Destination Directory: {0} (exists:{1})", destinationDirectory(), + destinationDirectory().exists())); + logger.fine(MessageFormat.format("Destination WAR: {0}", destinationArchiveFileName())); + } + + var out = new StringWriter(); + var stdout = new PrintWriter(out); + var err = new StringWriter(); + var stderr = new PrintWriter(err); + var jarTool = ToolProvider.findFirst("jar").orElseThrow(); + + String args; + if (logger.isLoggable(Level.FINER)) { + args = "-0cMvf"; + } else { + args = "-0cMf"; + } + + jarTool.run(stdout, stderr, args, + new File(destinationDirectory(), destinationArchiveFileName()).getAbsolutePath(), + "-C", stagingDirectory.getAbsolutePath(), "."); + + var errBuff = err.getBuffer(); + if (!errBuff.isEmpty()) { + throw new IOException(errBuff.toString()); + } else { + var outBuff = out.getBuffer(); + if (!outBuff.isEmpty() && logger.isLoggable(Level.INFO) && !silent()) { + logger.info(outBuff.toString()); + } + } + } + + /** + * Part of the {@link #execute} operation, create the manifest for the jar archive. + */ + protected void executeCreateManifest(File stagingDirectory) throws IOException { + var meta_inf_dir = new File(stagingDirectory, "META-INF"); + mkDirs(meta_inf_dir); + + var manifest = new File(meta_inf_dir, "MANIFEST.MF"); + + try (var fileWriter = Files.newBufferedWriter(manifest.toPath())) { + for (var manifestAttribute : manifestAttributes()) { + fileWriter.write(manifestAttribute.name() + ": " + manifestAttribute.value() + System.lineSeparator()); + } + } + } + + /** + * Part of the {@link #execute} operation, creates the {@code WEB-INF} staging directory. + */ + protected File executeCreateWebInfDirectory(File stagingDirectory) throws IOException { + var boot_inf = new File(stagingDirectory, "WEB-INF"); + mkDirs(boot_inf); + return boot_inf; + } + + /** + * Part of the {@link #execute} operation, configure the JAR launcher class. + */ + protected AbstractBootOperation launcherClass(String className) { + launcherClass_ = className; + return this; + } + + /** + * Retrieves the JAR launcher class fully-qualified name. + */ + protected String launcherClass() { + if (launcherClass_ == null) { + throw new IllegalArgumentException("ERROR: Spring boot launcher class required."); + } + return launcherClass_; + } + + /** + * Provides an attribute to put in the JAR manifest. + * + * @param name the attribute name to put in the manifest + * @param value the attribute value to put in the manifest + * @return this operation instance + */ + public AbstractBootOperation manifestAttribute(String name, String value) { + manifestAttributes_.add(new BootManifestAttribute(name, value)); + return this; + } + + /** + * Retrieves the list of attributes that will be put in the jar manifest. + */ + public List manifestAttributes() { + return manifestAttributes_; + } + + /** + * Provides a map of attributes to put in the jar manifest. + *

+ * A copy will be created to allow this map to be independently modifiable. + * + * @param attributes the attributes to put in the manifest + * @return this operation instance + * \ + */ + public AbstractBootOperation manifestAttributes(Collection attributes) { + manifestAttributes_.addAll(attributes); + return this; + } + + /** + * Creates a directory for the given file path, including any necessary but nonexistent parent directories. + */ + protected void mkDirs(File file) throws IOException { + if (!file.exists() && !file.mkdirs()) { + throw new IOException("ERROR: unable to create: " + file.getAbsolutePath()); + } + } + + /** + * Provides source directories that will be used for the jar archive creation. + * + * @param directories source directories + * @return this operation instance + */ + public AbstractBootOperation sourceDirectories(File... directories) { + sourceDirectories_.addAll(List.of(directories)); + return this; + } +} diff --git a/src/main/java/rife/bld/extension/BootJarOperation.java b/src/main/java/rife/bld/extension/BootJarOperation.java index c2ae1bd..9033016 100644 --- a/src/main/java/rife/bld/extension/BootJarOperation.java +++ b/src/main/java/rife/bld/extension/BootJarOperation.java @@ -17,82 +17,25 @@ package rife.bld.extension; import rife.bld.Project; -import rife.bld.WebProject; -import rife.bld.operations.AbstractOperation; import rife.tools.FileUtils; -import rife.tools.exceptions.FileUtilsErrorException; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; -import java.util.spi.ToolProvider; -import java.util.zip.CRC32; -public class BootJarOperation extends AbstractOperation { +/** + * Builds and creates a Sprint Boot executable java archive (JAR). + * + * @author Erik C. Thauvin + * @since 1.0 + */ +public class BootJarOperation extends AbstractBootOperation { private static final Logger LOGGER = Logger.getLogger(BootJarOperation.class.getName()); - private final CRC32 crc32_ = new CRC32(); private final List libJars_ = new ArrayList<>(); - private final List manifestAttributes_ = new ArrayList<>(); - private final List sourceDirectories_ = new ArrayList<>(); - private File destinationDirectory_; - private String destinationJarFileName_; - private String jarLauncherClass_ = "org.springframework.boot.loader.JarLauncher"; - private WebProject project_; - - private long computeCrc32Checksum(Path filePath) throws IOException { - crc32_.reset(); - crc32_.update(Files.readAllBytes(filePath)); - - return crc32_.getValue(); - } - - /** - * Retrieves the destination directory in which the JAR will be created. - * - * @return the JAR's destination directory - */ - public File destinationDirectory() { - return destinationDirectory_; - } - - /** - * Provides the destination directory in which the JAR will be created. - * - * @param directory the war destination directory - * @return this operation instance - * @since 1.5 - */ - public BootJarOperation destinationDirectory(File directory) throws IOException { - destinationDirectory_ = directory; - mkDirs(destinationDirectory_); - return this; - } - - /** - * Provides the destination file name that will be used for the JAR creation. - * - * @param name the war archive destination file name - * @return this operation instance - */ - public BootJarOperation destinationJarFileName(String name) { - destinationJarFileName_ = name; - return this; - } - - /** - * Retrieves the destination file name that will be used for the JAR creation. - * - * @return the war Jar's destination file name - */ - public String destinationJarFileName() { - return destinationJarFileName_; - } /** * Performs the BootJar operation. @@ -108,15 +51,15 @@ public class BootJarOperation extends AbstractOperation { var staging_dir = Files.createTempDirectory("bootjar").toFile(); try { - var boot_inf_dir = executeCreateBootInfDirectory(staging_dir); + var boot_inf_dir = executeCreateWebInfDirectory(staging_dir); executeCopyBootInfClassesFiles(boot_inf_dir); - executeCopyBootInfLibJars(boot_inf_dir); + executeCopyBootInfLibs(boot_inf_dir); executeCopyBootLoader(staging_dir); - executeCreateJar(staging_dir); + executeCreateArchive(staging_dir, LOGGER); if (!silent()) { - System.out.printf("The executable JAR (%s) was created in: %s%n", destinationJarFileName(), + System.out.printf("The executable JAR (%s) was created in: %s%n", destinationArchiveFileName(), destinationDirectory()); } @@ -125,27 +68,10 @@ public class BootJarOperation extends AbstractOperation { } } - /** - * Part of the {@link #execute} operation, copy the {@code BOOT-INF} classes. - */ - protected void executeCopyBootInfClassesFiles(File stagingBootInfDirectory) throws IOException { - var boot_inf_classes_dir = new File(stagingBootInfDirectory, "classes"); - mkDirs(boot_inf_classes_dir); - - for (var dir : sourceDirectories_) { - FileUtils.copyDirectory(dir, boot_inf_classes_dir); - } - - var resources_dir = new File(boot_inf_classes_dir, "resources"); - if (resources_dir.exists()) { - FileUtils.deleteDirectory(resources_dir); - } - } - /** * Part of the {@link #execute} operation, copy the {@code BOOT-INF} libs. */ - protected void executeCopyBootInfLibJars(File stagingBootInfDirectory) throws IOException { + protected void executeCopyBootInfLibs(File stagingBootInfDirectory) throws IOException { var boot_inf_lib_dir = new File(stagingBootInfDirectory, "lib"); mkDirs(boot_inf_lib_dir); @@ -154,226 +80,44 @@ public class BootJarOperation extends AbstractOperation { } } - /** - * Part of the {@link #execute} operation, copy the {@code spring-boot-loader} archive content to the staging directory. - */ - protected void executeCopyBootLoader(File stagingDirectory) throws FileUtilsErrorException { - if (project_.standaloneClasspathJars().isEmpty()) { - throw new IllegalArgumentException("ERROR: Spring Boot Loader required."); - } else { - for (var jar : project_.standaloneClasspathJars()) { - FileUtils.unzipFile(jar, stagingDirectory); - var meta_inf_dir = new File(stagingDirectory, "META-INF"); - if (meta_inf_dir.exists()) { - FileUtils.deleteDirectory(meta_inf_dir); - } - } - } - } - - /** - * Part of the {@link #execute} operation, creates the {@code BOOT-INF} staging directory. - */ - protected File executeCreateBootInfDirectory(File stagingDirectory) throws IOException { - var boot_inf = new File(stagingDirectory, "BOOT-INF"); - mkDirs(boot_inf); - return boot_inf; - } - - /** - * Part of the {@link #execute} operation, create the executable JAR from the staging directory. - */ - protected void executeCreateJar(File stagingDirectory) - throws IOException { - executeCreateManifest(stagingDirectory); - if (LOGGER.isLoggable(Level.FINE) && (!silent())) { - LOGGER.fine(MessageFormat.format("Staging Directory: {0} (exists:{1})", stagingDirectory, - stagingDirectory.exists())); - LOGGER.fine(MessageFormat.format("Destination Directory: {0} (exists:{1})", destinationDirectory(), - destinationDirectory().exists())); - LOGGER.fine(MessageFormat.format("Destination JAR: {0}", destinationJarFileName())); - } - -// try (var zipOutputStream = new ZipOutputStream(new FileOutputStream( -// new File(destinationDirectory(), destinationJarFileName())))) { -// zipOutputStream.setLevel(ZipOutputStream.STORED); -// zipOutputStream.setMethod(Deflater.NO_COMPRESSION); -// var stagingPath = stagingDirectory.toPath(); -// Files.walkFileTree(stagingPath, new SimpleFileVisitor<>() { -// public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException { -// var zipEntry = new ZipEntry(stagingPath.relativize(filePath).toString()); -// zipEntry.setMethod(ZipEntry.STORED); -// zipEntry.setSize(filePath.toFile().length()); -// zipEntry.setSize(filePath.toFile().length()); -// zipEntry.setCrc(computeCrc32Checksum(filePath)); -// zipOutputStream.putNextEntry(zipEntry); -// Files.copy(filePath, zipOutputStream); -// zipOutputStream.closeEntry(); -// -// if (logger.isLoggable(Level.FINER) && !silent()) { -// logger.finer(MessageFormat.format("Added to JAR: {0}", filePath)); -// } -// -// return FileVisitResult.CONTINUE; -// } -// }); -// } - - var out = new StringWriter(); - var stdout = new PrintWriter(out); - var err = new StringWriter(); - var stderr = new PrintWriter(err); - var jarTool = ToolProvider.findFirst("jar").orElseThrow(); - - String args; - if (LOGGER.isLoggable(Level.FINER)) { - args = "-0cMvf"; - } else { - args = "-0cMf"; - } - - jarTool.run(stdout, stderr, args, - new File(destinationDirectory(), destinationJarFileName()).getAbsolutePath(), - "-C", stagingDirectory.getAbsolutePath(), "."); - - var errBuff = err.getBuffer(); - if (!errBuff.isEmpty()) { - throw new IOException(errBuff.toString()); - } else { - var outBuff = out.getBuffer(); - if (!outBuff.isEmpty()) { - if (LOGGER.isLoggable(Level.INFO) && !silent()) { - LOGGER.info(outBuff.toString()); - } - } - } - } - - /** - * Part of the {@link #execute} operation, create the manifest for the jar archive. - */ - protected void executeCreateManifest(File stagingDirectory) throws IOException { - var meta_inf_dir = new File(stagingDirectory, "META-INF"); - mkDirs(meta_inf_dir); - - var manifest = new File(meta_inf_dir, "MANIFEST.MF"); - - try (var fileWriter = new FileWriter(manifest)) { - for (var manifestAttribute : manifestAttributes()) { - fileWriter.write(manifestAttribute.name + ": " + manifestAttribute.value + System.lineSeparator()); - } - } - } - /** * Configures the operation from a {@link Project}. - * - * @param project the project to configure the operation from */ - public BootJarOperation fromProject(WebProject project) throws IOException { + public AbstractBootOperation fromProject(Project project) throws IOException { project_ = project; - return manifestAttributes( - List.of( - new ManifestAttribute("Manifest-Version", "1.0"), - new ManifestAttribute("Main-Class", jarLauncherClass()), - new ManifestAttribute("Start-Class", project_.mainClass()) - )) + return bootInfLibs(project_.compileClasspathJars()) + .bootInfLibs(project_.runtimeClasspathJars()) + .launcherClass("org.springframework.boot.loader.JarLauncher") + .manifestAttributes( + List.of( + new BootManifestAttribute("Manifest-Version", "1.0"), + new BootManifestAttribute("Main-Class", launcherClass()), + new BootManifestAttribute("Start-Class", project_.mainClass())) + ) .destinationDirectory(project.buildDistDirectory()) - .destinationJarFileName(project.jarFileName()) - .libJars(project_.compileClasspathJars()) - .libJars(project_.runtimeClasspathJars()) + .destinationArchiveFileName(project_.archiveBaseName() + "-" + project_.version() + "-boot.jar") .sourceDirectories(project_.buildMainDirectory(), project_.srcMainResourcesDirectory()); } /** - * Part of the {@link #execute} operation, configure the JAR launcher class. - */ - protected BootJarOperation jarLauncherClass(String className) { - jarLauncherClass_ = className; - return this; - } - - /** - * Retrieves the JAR launcher class fully-qualified name. - */ - protected String jarLauncherClass() { - return jarLauncherClass_; - } - - /** - * Provides library JARs that will be used for the jar archive creation. - * - * @param jar Java archive file - * @return this operation instance - */ - public BootJarOperation libJars(File... jar) { - libJars_.addAll(List.of(jar)); - return this; - } - - /** - * Provides library JARs that will be used for the jar archive creation. + * Provides library JARs that will be used for the JAR creation. * * @param jars Java archive files * @return this operation instance */ - public BootJarOperation libJars(Collection jars) { + public BootJarOperation bootInfLibs(Collection jars) { libJars_.addAll(jars); return this; } /** - * Provides an attribute to put in the JAR manifest. + * Provides library JARs that will be used for the JAR creation. * - * @param name the attribute name to put in the manifest - * @param value the attribute value to put in the manifest + * @param jar Java archive file * @return this operation instance */ - public BootJarOperation manifestAttribute(String name, String value) { - manifestAttributes_.add(new ManifestAttribute(name, value)); + public BootJarOperation bootInfLibs(File... jar) { + libJars_.addAll(List.of(jar)); return this; } - - /** - * Retrieves the list of attributes that will be put in the jar manifest. - */ - public List manifestAttributes() { - return manifestAttributes_; - } - - /** - * Provides a map of attributes to put in the jar manifest. - *

- * A copy will be created to allow this map to be independently modifiable. - * - * @param attributes the attributes to put in the manifest - * @return this operation instance - * \ - */ - public BootJarOperation manifestAttributes(Collection attributes) { - manifestAttributes_.addAll(attributes); - return this; - } - - private void mkDirs(File file) throws IOException { - if (!file.exists()) { - if (!file.mkdirs()) { - throw new IOException("ERROR: unable to create: " + file.getAbsolutePath()); - } - } - } - - /** - * Provides source directories that will be used for the jar archive creation. - * - * @param directories source directories - * @return this operation instance - */ - public BootJarOperation sourceDirectories(File... directories) { - sourceDirectories_.addAll(List.of(directories)); - return this; - } - - public record ManifestAttribute(String name, String value) { - } } \ No newline at end of file diff --git a/src/main/java/rife/bld/extension/BootManifestAttribute.java b/src/main/java/rife/bld/extension/BootManifestAttribute.java new file mode 100644 index 0000000..f20c341 --- /dev/null +++ b/src/main/java/rife/bld/extension/BootManifestAttribute.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 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 + * + * https://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 rife.bld.extension; + +/** + * Constructs a new manifest attribute. + * + * @param name The attribute name + * @param value The attribute value + */ +public record BootManifestAttribute(String name, String value) { +} diff --git a/src/main/java/rife/bld/extension/BootWarOperation.java b/src/main/java/rife/bld/extension/BootWarOperation.java new file mode 100644 index 0000000..3926c58 --- /dev/null +++ b/src/main/java/rife/bld/extension/BootWarOperation.java @@ -0,0 +1,163 @@ +/* + * Copyright 2023 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 + * + * https://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 rife.bld.extension; + +import rife.bld.Project; +import rife.tools.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Logger; + +/** + * Builds and creates a Sprint Boot web archive (WAR). + * + * @author Erik C. Thauvin + * @since 1.0 + */ +public class BootWarOperation extends AbstractBootOperation { + private static final Logger LOGGER = Logger.getLogger(BootWarOperation.class.getName()); + private final List webInfLibs_ = new ArrayList<>(); + private final List webInfProvidedLibs_ = new ArrayList<>(); + + /** + * Performs the BootJar operation. + */ + @Override + public void execute() throws Exception { + if (project_ == null) { + throw new IllegalArgumentException("ERROR: project required."); + } else if (project_.mainClass() == null) { + throw new IllegalArgumentException("ERROR: project mainClass required."); + } + + var staging_dir = Files.createTempDirectory("bootwar").toFile(); + + try { + var boot_web_inf_dir = executeCreateWebInfDirectory(staging_dir); + executeCopyBootInfClassesFiles(boot_web_inf_dir); + executeCopyWebInfLib(boot_web_inf_dir); + executeCopyWebInfProvidedLib(boot_web_inf_dir); + executeCopyBootLoader(staging_dir); + + executeCreateArchive(staging_dir, LOGGER); + + if (!silent()) { + System.out.printf("The WAR (%s) was created in: %s%n", destinationArchiveFileName(), + destinationDirectory()); + } + + } finally { + FileUtils.deleteDirectory(staging_dir); + } + } + + /** + * Part of the {@link #execute} operation, copy the {@code BOOT-INF} libs. + */ + protected void executeCopyWebInfLib(File stagingBootInfDirectory) throws IOException { + var boot_inf_lib_dir = new File(stagingBootInfDirectory, "lib"); + mkDirs(boot_inf_lib_dir); + + for (var jar : webInfLibs_) { + Files.copy(jar.toPath(), boot_inf_lib_dir.toPath().resolve(jar.getName())); + } + } + + /** + * Part of the {@link #execute} operation, copy the {@code BOOT-INF} libs. + */ + protected void executeCopyWebInfProvidedLib(File stagingBootInfDirectory) throws IOException { + var boot_inf_lib_dir = new File(stagingBootInfDirectory, "lib"); + mkDirs(boot_inf_lib_dir); + + for (var jar : webInfProvidedLibs_) { + Files.copy(jar.toPath(), boot_inf_lib_dir.toPath().resolve(jar.getName())); + } + } + + /** + * Configures the operation from a {@link Project}. + * + * @param project the project to configure the operation from + */ + public AbstractBootOperation fromProject(Project project) throws IOException { + project_ = project; + return webInfLibs(project.compileClasspathJars()) + .webInfLibs(project.runtimeClasspathJars()) + .webInfLibs(project.buildDistDirectory()) + // TODO add provided libs + .launcherClass("org.springframework.boot.loader.WarLauncher") + .manifestAttributes( + List.of( + new BootManifestAttribute("Manifest-Version", "1.0"), + new BootManifestAttribute("Main-Class", launcherClass()), + new BootManifestAttribute("Start-Class", project.mainClass()) + )) + .destinationDirectory(project.buildDistDirectory()) + .destinationArchiveFileName(project.archiveBaseName() + "-" + project.version() + "-boot.war") + .sourceDirectories(project.buildMainDirectory(), project.srcMainResourcesDirectory()); + } + + /** + * Provides library JARs that will be used for the WAR creation. + * + * @param jars Java archive files + * @return this operation instance + */ + public BootWarOperation webInfLibs(Collection jars) { + webInfLibs_.addAll(jars); + return this; + } + + /** + * Provides library JARs that will be used for the WAR creation. + * + * @param jar Java archive file + * @return this operation instance + */ + public BootWarOperation webInfLibs(File... jar) { + webInfLibs_.addAll(List.of(jar)); + return this; + } + + /** + * Provides library JARs that will be used for the WAR creation in {@code /WEB-INF/lib-provided}. + * + * @param jars Java archive files + * @return this operation instance + */ + public BootWarOperation webInfProvidedLibs(Collection jars) { + webInfProvidedLibs_.addAll(jars); + return this; + } + + /** + * Provides the library JARs that will be used for the WAR creation in {@code /WEB-INF/lib-provided}. + * + * @param jar Java archive file + * @return this operation instance + */ + public BootWarOperation webInfProvidedLibs(File... jar) { + webInfProvidedLibs_.addAll(List.of(jar)); + return this; + } +} \ No newline at end of file diff --git a/src/test/java/rife/bld/extension/BootJarOperationTest.java b/src/test/java/rife/bld/extension/BootJarOperationTest.java index 6db9c77..5a167cb 100644 --- a/src/test/java/rife/bld/extension/BootJarOperationTest.java +++ b/src/test/java/rife/bld/extension/BootJarOperationTest.java @@ -16,7 +16,5 @@ package rife.bld.extension; -import org.junit.jupiter.api.Test; - class BootJarOperationTest { } \ No newline at end of file