From b769c8d6a06482d58c06de2c94c4f234c3c665e3 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 11 Jul 2019 11:43:52 +0100 Subject: [PATCH] Migrate from Ant to Commons Compress Closes gh-951 --- initializr-web/pom.xml | 4 +- .../web/project/MainController.java | 115 +++++++++------ .../AbstractInitializrIntegrationTests.java | 134 +++++++++++------- pom.xml | 17 +-- 4 files changed, 158 insertions(+), 112 deletions(-) diff --git a/initializr-web/pom.xml b/initializr-web/pom.xml index ee97b7de..46fe6046 100644 --- a/initializr-web/pom.xml +++ b/initializr-web/pom.xml @@ -41,8 +41,8 @@ spring-hateoas - org.apache.ant - ant + org.apache.commons + commons-compress diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java b/initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java index 47d52724..8603d02a 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java @@ -18,6 +18,7 @@ package io.spring.initializr.web.project; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -25,6 +26,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import io.spring.initializr.generator.buildsystem.BuildSystem; import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem; @@ -43,13 +47,16 @@ import io.spring.initializr.web.mapper.InitializrMetadataVersion; import io.spring.initializr.web.support.Agent; import io.spring.initializr.web.support.Agent.AgentId; import io.spring.initializr.web.support.CommandLineHelpGenerator; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveOutputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.apache.commons.compress.archivers.zip.UnixStat; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.tools.ant.Project; -import org.apache.tools.ant.taskdefs.Tar; -import org.apache.tools.ant.taskdefs.Zip; -import org.apache.tools.ant.types.TarFileSet; -import org.apache.tools.ant.types.ZipFileSet; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; @@ -221,26 +228,8 @@ public class MainController extends AbstractInitializrController { @ResponseBody public ResponseEntity springZip(ProjectRequest request) throws IOException { ProjectGenerationResult result = this.projectGenerationInvoker.invokeProjectStructureGeneration(request); - File dir = result.getRootDirectory().toFile(); - Path archive = this.projectGenerationInvoker.createDistributionFile(result.getRootDirectory(), ".zip"); - String wrapperScript = getWrapperScript(result.getProjectDescription()); - Zip zip = new Zip(); - zip.setProject(new Project()); - zip.setDefaultexcludes(false); - ZipFileSet set = new ZipFileSet(); - set.setDir(dir); - set.setFileMode("755"); - set.createInclude().setName(wrapperScript); - set.setDefaultexcludes(false); - zip.addFileset(set); - set = new ZipFileSet(); - set.setDir(dir); - set.setIncludes("**,"); - set.createExclude().setName(wrapperScript); - set.setDefaultexcludes(false); - zip.addFileset(set); - zip.setDestFile(archive.toFile().getCanonicalFile()); - zip.execute(); + Path archive = createArchive(result, "zip", ZipArchiveOutputStream::new, ZipArchiveEntry::new, + (entry, mode) -> entry.setUnixMode(mode)); return upload(archive, result.getRootDirectory(), generateFileName(request, "zip"), "application/zip"); } @@ -248,31 +237,63 @@ public class MainController extends AbstractInitializrController { @ResponseBody public ResponseEntity springTgz(ProjectRequest request) throws IOException { ProjectGenerationResult result = this.projectGenerationInvoker.invokeProjectStructureGeneration(request); - File dir = result.getRootDirectory().toFile(); - Path download = this.projectGenerationInvoker.createDistributionFile(result.getRootDirectory(), ".tar.gz"); - String wrapperScript = getWrapperScript(result.getProjectDescription()); - Tar zip = new Tar(); - zip.setProject(new Project()); - zip.setDefaultexcludes(false); - TarFileSet set = zip.createTarFileSet(); - set.setDir(dir); - set.setFileMode("755"); - set.createInclude().setName(wrapperScript); - set.setDefaultexcludes(false); - set = zip.createTarFileSet(); - set.setDir(dir); - set.setIncludes("**,"); - set.createExclude().setName(wrapperScript); - set.setDefaultexcludes(false); - zip.setDestFile(download.toFile().getCanonicalFile()); - Tar.TarCompressionMethod method = new Tar.TarCompressionMethod(); - method.setValue("gzip"); - zip.setCompression(method); - zip.execute(); - return upload(download, result.getRootDirectory(), generateFileName(request, "tar.gz"), + Path archive = createArchive(result, "tar.gz", this::createTarArchiveOutputStream, TarArchiveEntry::new, + (entry, mode) -> entry.setMode(mode)); + return upload(archive, result.getRootDirectory(), generateFileName(request, "tar.gz"), "application/x-compress"); } + private TarArchiveOutputStream createTarArchiveOutputStream(OutputStream output) { + try { + return new TarArchiveOutputStream(new GzipCompressorOutputStream(output)); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + private Path createArchive(ProjectGenerationResult result, String fileExtension, + Function archiveOutputStream, + BiFunction archiveEntry, BiConsumer setMode) throws IOException { + Path archive = this.projectGenerationInvoker.createDistributionFile(result.getRootDirectory(), + "." + fileExtension); + String wrapperScript = getWrapperScript(result.getProjectDescription()); + try (ArchiveOutputStream output = archiveOutputStream.apply(Files.newOutputStream(archive))) { + Files.walk(result.getRootDirectory()).filter((path) -> !result.getRootDirectory().equals(path)) + .forEach((path) -> { + try { + String entryName = getEntryName(result.getRootDirectory(), path); + T entry = archiveEntry.apply(path.toFile(), entryName); + setMode.accept(entry, getUnixMode(wrapperScript, entryName, path)); + output.putArchiveEntry(entry); + if (!Files.isDirectory(path)) { + Files.copy(path, output); + } + output.closeArchiveEntry(); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + }); + } + return archive; + } + + private String getEntryName(Path root, Path path) { + String entryName = root.relativize(path).toString().replace('\\', '/'); + if (Files.isDirectory(path)) { + entryName += "/"; + } + return entryName; + } + + private int getUnixMode(String wrapperScript, String entryName, Path path) { + if (Files.isDirectory(path)) { + return UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM; + } + return UnixStat.FILE_FLAG | (entryName.equals(wrapperScript) ? 0755 : UnixStat.DEFAULT_FILE_PERM); + } + private String generateFileName(ProjectRequest request, String extension) { String candidate = (StringUtils.hasText(request.getArtifactId()) ? request.getArtifactId() : this.metadataProvider.get().getArtifactId().getContent()); diff --git a/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java index 04ab46d7..5e1b6b2c 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java @@ -16,15 +16,19 @@ package io.spring.initializr.web; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Enumeration; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -32,10 +36,11 @@ import io.spring.initializr.generator.spring.test.ProjectAssert; import io.spring.initializr.web.AbstractInitializrIntegrationTests.Config; import io.spring.initializr.web.mapper.InitializrMetadataVersion; import io.spring.initializr.web.support.InitializrMetadataUpdateStrategy; -import org.apache.tools.ant.Project; -import org.apache.tools.ant.taskdefs.ExecTask; -import org.apache.tools.ant.taskdefs.Expand; -import org.apache.tools.ant.taskdefs.Untar; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.json.JSONException; import org.json.JSONObject; import org.junit.jupiter.api.BeforeEach; @@ -212,59 +217,45 @@ public abstract class AbstractInitializrIntegrationTests { } } - private void untar(Path archiveFile, Path project) { - if (!runningOnWindows()) { - createProjectDir(project); - ExecTask execTask = new ExecTask(); - execTask.setProject(new Project()); - execTask.setExecutable("tar"); - execTask.createArg().setValue("-C"); - execTask.createArg().setValue(project.toFile().getAbsolutePath()); - execTask.createArg().setValue("-xf"); - execTask.createArg().setValue(archiveFile.toFile().getAbsolutePath()); - execTask.execute(); - } - else { - Untar expand = new Untar(); - expand.setProject(new Project()); - expand.setDest(project.toFile()); - expand.setSrc(archiveFile.toFile()); - Untar.UntarCompressionMethod method = new Untar.UntarCompressionMethod(); - method.setValue("gzip"); - expand.setCompression(method); - expand.execute(); + private void untar(Path archiveFile, Path project) throws IOException { + try (TarArchiveInputStream input = new TarArchiveInputStream( + new GzipCompressorInputStream(Files.newInputStream(archiveFile)))) { + TarArchiveEntry entry = null; + while ((entry = input.getNextTarEntry()) != null) { + Path path = project.resolve(entry.getName()); + if (entry.isDirectory()) { + Files.createDirectories(path); + } + else { + Files.createDirectories(path.getParent()); + Files.write(path, StreamUtils.copyToByteArray(input)); + } + Files.setPosixFilePermissions(path, getPosixFilePermissions(entry.getMode())); + } } } - private void createProjectDir(Path project) { - ExecTask execTask = new ExecTask(); - execTask.setProject(new Project()); - execTask.setExecutable("mkdir"); - execTask.createArg().setValue(project.toFile().getAbsolutePath()); - execTask.execute(); - } - - private void unzip(Path archiveFile, Path project) { - if (!runningOnWindows()) { - ExecTask execTask = new ExecTask(); - execTask.setProject(new Project()); - execTask.setExecutable("unzip"); - execTask.createArg().setValue(archiveFile.toFile().getAbsolutePath()); - execTask.createArg().setValue("-d"); - execTask.createArg().setValue(project.toFile().getAbsolutePath()); - execTask.execute(); - } - else { - Expand expand = new Expand(); - expand.setProject(new Project()); - expand.setDest(project.toFile()); - expand.setSrc(archiveFile.toFile()); - expand.execute(); + private void unzip(Path archiveFile, Path project) throws IOException { + try (ZipFile zip = new ZipFile(archiveFile.toFile())) { + Enumeration entries = zip.getEntries(); + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + Path path = project.resolve(entry.getName()); + if (entry.isDirectory()) { + Files.createDirectories(path); + } + else { + Files.createDirectories(path.getParent()); + Files.write(path, StreamUtils.copyToByteArray(zip.getInputStream(entry))); + } + Files.setPosixFilePermissions(path, getPosixFilePermissions(entry.getUnixMode())); + } } } - private boolean runningOnWindows() { - return File.separatorChar == '\\'; + private Set getPosixFilePermissions(int unixMode) { + return Arrays.stream(BitMaskFilePermission.values()).filter((permission) -> permission.permitted(unixMode)) + .map(BitMaskFilePermission::getFilePermission).collect(Collectors.toSet()); } protected Path writeArchive(byte[] body) throws IOException { @@ -320,4 +311,43 @@ public abstract class AbstractInitializrIntegrationTests { } + private enum BitMaskFilePermission { + + OWNER_READ(0400), + + OWNER_WRITE(0200), + + OWNER_EXECUTE(0100), + + GROUP_READ(0040), + + GROUP_WRITE(0020), + + GROUP_EXECUTE(0010), + + OTHERS_READ(0004), + + OTHERS_WRITE(0002), + + OTHERS_EXECUTE(0001); + + private int mask; + + private PosixFilePermission filePermission; + + BitMaskFilePermission(int mask) { + this.mask = mask; + this.filePermission = PosixFilePermission.valueOf(this.name()); + } + + boolean permitted(int unixMode) { + return (this.mask & unixMode) == this.mask; + } + + PosixFilePermission getFilePermission() { + return this.filePermission; + } + + } + } diff --git a/pom.xml b/pom.xml index 0247080a..96f81a66 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ 1.8 ${basedir} UTF-8 + 1.18 5.4.2 3.6.1 1.3.3 @@ -105,6 +106,11 @@ ${revision} test-jar + + org.apache.commons + commons-compress + ${commons-compress.version} + org.apache.maven maven-resolver-provider @@ -146,17 +152,6 @@ android-json 0.0.20131108.vaadin1 - - org.apache.ant - ant - 1.10.2 - - - org.apache.ant - ant-launcher - - -