You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2020/06/12 04:01:29 UTC

[tomee-patch-plugin] 04/04: Very nearly functional jar-patching

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 <da...@gmail.com>
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