This is an automated email from the ASF dual-hosted git repository. dblevins pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomee-patch-plugin.git
commit ec32c94ae0035b44d941e43a3732bd53a182bedb Author: David Blevins <[email protected]> AuthorDate: Thu Jun 11 21:00:58 2020 -0700 Very nearly functional jar-patching --- .../java/org/apache/tomee/patch/core/Clazz.java | 57 +++++++++ .../main/java/org/apache/tomee/patch/core/Log.java | 35 ++++++ .../java/org/apache/tomee/patch/core/NullLog.java | 99 ++++++++++++++++ .../apache/tomee/patch/core/Transformation.java | 128 ++++++++++++++++++++- .../org/apache/tomee/patch/plugin/MavenLog.java | 76 ++++++++++++ .../org/apache/tomee/patch/plugin/PatchMojo.java | 26 ++++- 6 files changed, 412 insertions(+), 9 deletions(-) diff --git a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Clazz.java b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Clazz.java new file mode 100644 index 0000000..2c1a09e --- /dev/null +++ b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Clazz.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.tomee.patch.core; + +import java.io.File; +import java.util.Objects; + +public class Clazz { + private final String name; + private final String prefix; + private final File file; + + public Clazz(final String name, final File file) { + this.name = name; + this.prefix = name.replaceAll("\\.class$", ""); + this.file = file; + } + + public String getPrefix() { + return prefix; + } + + public String getName() { + return name; + } + + public File getFile() { + return file; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Clazz clazz = (Clazz) o; + return name.equals(clazz.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Log.java b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Log.java new file mode 100644 index 0000000..1819d3f --- /dev/null +++ b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Log.java @@ -0,0 +1,35 @@ +package org.apache.tomee.patch.core; + +public interface Log { + boolean isDebugEnabled(); + + void debug(CharSequence var1); + + void debug(CharSequence var1, Throwable var2); + + void debug(Throwable var1); + + boolean isInfoEnabled(); + + void info(CharSequence var1); + + void info(CharSequence var1, Throwable var2); + + void info(Throwable var1); + + boolean isWarnEnabled(); + + void warn(CharSequence var1); + + void warn(CharSequence var1, Throwable var2); + + void warn(Throwable var1); + + boolean isErrorEnabled(); + + void error(CharSequence var1); + + void error(CharSequence var1, Throwable var2); + + void error(Throwable var1); +} diff --git a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/NullLog.java b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/NullLog.java new file mode 100644 index 0000000..180c346 --- /dev/null +++ b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/NullLog.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.tomee.patch.core; + +public class NullLog implements Log { + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public void debug(final CharSequence var1) { + + } + + @Override + public void debug(final CharSequence var1, final Throwable var2) { + + } + + @Override + public void debug(final Throwable var1) { + + } + + @Override + public boolean isInfoEnabled() { + return false; + } + + @Override + public void info(final CharSequence var1) { + + } + + @Override + public void info(final CharSequence var1, final Throwable var2) { + + } + + @Override + public void info(final Throwable var1) { + + } + + @Override + public boolean isWarnEnabled() { + return false; + } + + @Override + public void warn(final CharSequence var1) { + + } + + @Override + public void warn(final CharSequence var1, final Throwable var2) { + + } + + @Override + public void warn(final Throwable var1) { + + } + + @Override + public boolean isErrorEnabled() { + return false; + } + + @Override + public void error(final CharSequence var1) { + + } + + @Override + public void error(final CharSequence var1, final Throwable var2) { + + } + + @Override + public void error(final Throwable var1) { + + } +} diff --git a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java index 8f05c8e..53bb1d8 100644 --- a/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java +++ b/tomee-patch-core/src/main/java/org/apache/tomee/patch/core/Transformation.java @@ -25,27 +25,52 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; public class Transformation { - private Transformation() { + private final List<Clazz> classes = new ArrayList<Clazz>(); + private final Log log; + + public Transformation() { + this.log = new NullLog(); + } + + + public Transformation(final List<Clazz> classes, final Log log) { + this.classes.addAll(classes); + this.log = log; } public static File transform(final File jar) throws IOException { + return new Transformation().transformArchive(jar); + } + + public File transformArchive(final File jar) throws IOException { final File tempFile = File.createTempFile(jar.getName(), ".transformed"); try (final InputStream inputStream = IO.read(jar)) { try (final OutputStream outputStream = IO.write(tempFile)) { - scanJar(inputStream, outputStream); + final Jar old = Jar.enter(jar.getName()); + try { + scanJar(inputStream, outputStream); + } finally { + Jar.exit(old); + } return tempFile; } } } - private static void scanJar(final InputStream inputStream, final OutputStream outputStream) throws IOException { + private void scanJar(final InputStream inputStream, final OutputStream outputStream) throws IOException { final ZipInputStream zipInputStream = new ZipInputStream(inputStream); final ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream); @@ -53,9 +78,20 @@ public class Transformation { while ((oldEntry = zipInputStream.getNextEntry()) != null) { // TODO: the name may be changed in transformation final String path = oldEntry.getName(); + + /* + * If this entry has been patched, skip it + * We will add the patched version at the end + */ + if (isPatched(path)) { + log.debug("Skipping class " + path); + IO.copy(zipInputStream, skipped); + continue; + } + final ZipEntry newEntry = new ZipEntry(path); - copyAttributes(oldEntry, newEntry); +// copyAttributes(oldEntry, newEntry); zipOutputStream.putNextEntry(newEntry); @@ -63,7 +99,12 @@ public class Transformation { if (path.endsWith(".class")) { scanClass(zipInputStream, zipOutputStream); } else if (isZip(path)) { - scanJar(zipInputStream, zipOutputStream); + final Jar old = Jar.enter(path); + try { + scanJar(zipInputStream, zipOutputStream); + } finally { + Jar.exit(old); // restore the old state + } } else { IO.copy(zipInputStream, zipOutputStream); } @@ -71,6 +112,23 @@ public class Transformation { zipOutputStream.closeEntry(); } } + + // If we skipped any classes, add them now + if (Jar.current().hasPatches()) { + log.info("Patching " + Jar.current().getName()); + for (final Clazz clazz : Jar.current().getSkipped()) { + log.info("Applying patch " + clazz.getName()); + + final ZipEntry newEntry = new ZipEntry(clazz.getName()); + zipOutputStream.putNextEntry(newEntry); + + // Run any transformations on these classes as well + scanClass(IO.read(clazz.getFile()), zipOutputStream); + + zipOutputStream.closeEntry(); + } + } + zipOutputStream.close(); } @@ -98,4 +156,64 @@ public class Transformation { outputStream.write(bytes); } + public static class Jar { + private static final AtomicReference<Jar> current = new AtomicReference<>(new Jar("<none>")); + + private final Set<Clazz> patches = new HashSet<>(); + private final String name; + + public Jar(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public boolean hasPatches() { + return patches.size() > 0; + } + + public static Jar current() { + return current.get(); + } + + public static Jar enter(final String name) { + return current.getAndSet(new Jar(name)); + } + + public static void exit(final Jar oldJar) { + current.getAndSet(oldJar); + } + + public Collection<Clazz> getSkipped() { + return patches; + } + + /** + * Select all classes that are a patch for the specified class. + * This will also add any applicable inner-classes of the specified class + */ + public void patch(final Clazz clazz, final List<Clazz> potentialPatches) { + potentialPatches.stream() + .filter(potentialPatch -> potentialPatch.getName().startsWith(clazz.getPrefix())) + .forEach(patches::add); + } + } + + private boolean isPatched(final String path) { + for (final Clazz clazz : classes) { + if (path.startsWith(clazz.getPrefix())) { + Jar.current().patch(clazz, classes); + return true; + } + } + return false; + } + + private static final OutputStream skipped = new OutputStream() { + @Override + public void write(final int b) { + } + }; } diff --git a/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/MavenLog.java b/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/MavenLog.java new file mode 100644 index 0000000..9a088b2 --- /dev/null +++ b/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/MavenLog.java @@ -0,0 +1,76 @@ +package org.apache.tomee.patch.plugin; + + +import org.apache.maven.plugin.logging.Log; + +public class MavenLog implements org.apache.tomee.patch.core.Log { + private final Log log; + + public MavenLog(final Log log) { + this.log = log; + } + + public boolean isDebugEnabled() { + return log.isDebugEnabled(); + } + + public void debug(final CharSequence charSequence) { + log.debug(charSequence); + } + + public void debug(final CharSequence charSequence, final Throwable throwable) { + log.debug(charSequence, throwable); + } + + public void debug(final Throwable throwable) { + log.debug(throwable); + } + + public boolean isInfoEnabled() { + return log.isInfoEnabled(); + } + + public void info(final CharSequence charSequence) { + log.info(charSequence); + } + + public void info(final CharSequence charSequence, final Throwable throwable) { + log.info(charSequence, throwable); + } + + public void info(final Throwable throwable) { + log.info(throwable); + } + + public boolean isWarnEnabled() { + return log.isWarnEnabled(); + } + + public void warn(final CharSequence charSequence) { + log.warn(charSequence); + } + + public void warn(final CharSequence charSequence, final Throwable throwable) { + log.warn(charSequence, throwable); + } + + public void warn(final Throwable throwable) { + log.warn(throwable); + } + + public boolean isErrorEnabled() { + return log.isErrorEnabled(); + } + + public void error(final CharSequence charSequence) { + log.error(charSequence); + } + + public void error(final CharSequence charSequence, final Throwable throwable) { + log.error(charSequence, throwable); + } + + public void error(final Throwable throwable) { + log.error(throwable); + } +} diff --git a/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java b/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java index bcf8ab8..e4e4ed8 100644 --- a/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java +++ b/tomee-patch-plugin/src/main/java/org/apache/tomee/patch/plugin/PatchMojo.java @@ -17,8 +17,6 @@ package org.apache.tomee.patch.plugin; import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.factory.ArtifactFactory; -import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -28,10 +26,11 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.toolchain.Toolchain; import org.apache.maven.toolchain.ToolchainManager; +import org.apache.tomee.patch.core.Clazz; import org.apache.tomee.patch.core.Is; +import org.apache.tomee.patch.core.Transformation; import org.codehaus.plexus.compiler.Compiler; import org.codehaus.plexus.compiler.CompilerConfiguration; import org.codehaus.plexus.compiler.CompilerMessage; @@ -39,7 +38,9 @@ import org.codehaus.plexus.compiler.CompilerResult; import org.codehaus.plexus.compiler.manager.CompilerManager; import org.codehaus.plexus.compiler.manager.NoSuchCompilerException; import org.tomitribe.jkta.usage.Dir; +import org.tomitribe.jkta.util.Paths; import org.tomitribe.util.Files; +import org.tomitribe.util.IO; import org.tomitribe.util.Zips; import java.io.File; @@ -159,7 +160,7 @@ public class PatchMojo extends AbstractMojo { public void execute() throws MojoExecutionException, CompilationFailureException { try { Files.mkdir(patchClasspathDirectory); - + // Select the zip files and jars we'll be potentially patching final List<Artifact> artifacts = getPatchArtifacts(); @@ -168,11 +169,28 @@ public class PatchMojo extends AbstractMojo { compile(patchSourceDirectory, jars); + final List<Clazz> clazzes = classes(); + + final Transformation transformation = new Transformation(clazzes, new MavenLog(getLog())); + for (final Artifact artifact : artifacts) { + final File file = artifact.getFile(); + getLog().debug("Patching " + file.getAbsolutePath()); + final File patched = transformation.transformArchive(file); + IO.copy(patched, file); + } + } catch (IOException e) { throw new MojoExecutionException("Error occurred during execution", e); } } + private List<Clazz> classes() { + return Dir.from(buildDirectory).files() + .filter(file -> file.getName().endsWith(".class")) + .map(file -> new Clazz(Paths.childPath(buildDirectory, file), file)) + .collect(Collectors.toList()); + } + /** * Any zip files contained in the Artifact set should be extracted * Any jar files contained in the Artifact set will be returned as-is
