From e5aa85774faadd6362f6d9d68da8511b13549ff7 Mon Sep 17 00:00:00 2001
From: mck <mick@semb.wever.org>
Date: Sun, 22 Jun 2014 01:31:16 +0200
Subject: [PATCH] =?UTF-8?q?LOG4J2-673=20=E2=80=93=20plugin=20preloading=20?=
 =?UTF-8?q?fails=20in=20shaded=20jar=20files?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Support for plugin preloading through the standard javax.annotation.processing tool was adding in LOG4J2-595

But the plugin processor always creates and stores the processed "Plugin" annotated classes into the same "Log4j2Plugins.dat" file. This works fine when the classpath consists of individual jar files, but fails when shaded jar files are used.

Add a ResourceTransformer that can be used in conjunction with the maven-shaded-plugin to merge multiple Log4j2Plugins.dat into one.
---
 log4j-core/pom.xml                                 |   7 ++
 .../core/config/plugins/processor/PluginCache.java |  45 ++++++-----
 .../processor/ShadedResourceTransformer.java       |  70 ++++++++++++++++
 .../processor/ShadedResourceTransformerTest.java   |  89 +++++++++++++++++++++
 .../resources/META-INF/Log4j2Plugins-test-0.dat    | Bin 0 -> 10155 bytes
 .../resources/META-INF/Log4j2Plugins-test-1.dat    | Bin 0 -> 1052 bytes
 6 files changed, 192 insertions(+), 19 deletions(-)
 create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformer.java
 create mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformerTest.java
 create mode 100644 log4j-core/src/test/resources/META-INF/Log4j2Plugins-test-0.dat
 create mode 100644 log4j-core/src/test/resources/META-INF/Log4j2Plugins-test-1.dat

diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index b2835e1..374fbe7 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -105,6 +105,13 @@
       <scope>compile</scope>
       <optional>true</optional>
     </dependency>
+    <!-- Used by ShadedResourceTransformer -->
+    <dependency>
+      <groupId>org.apache.maven.plugins</groupId>
+      <artifactId>maven-shade-plugin</artifactId>
+      <version>2.3</version>
+      <optional>true</optional>
+    </dependency>
 
     <!-- TEST DEPENDENCIES -->
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java
index fefed12..c6852d1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java
@@ -22,6 +22,7 @@ import java.io.BufferedOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
 import java.util.Enumeration;
@@ -29,6 +30,7 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry;
+import org.apache.logging.log4j.core.util.Closer;
 
 /**
  *
@@ -84,27 +86,32 @@ public class PluginCache {
         pluginCategories.clear();
         while (resources.hasMoreElements()) {
             final URL url = resources.nextElement();
-            final DataInputStream in = new DataInputStream(new BufferedInputStream(url.openStream()));
-            try {
-                final int count = in.readInt();
-                for (int i = 0; i < count; i++) {
-                    final String category = in.readUTF();
-                    final ConcurrentMap<String, PluginEntry> m = pluginCategories.getCategory(category);
-                    final int entries = in.readInt();
-                    for (int j = 0; j < entries; j++) {
-                        final PluginEntry entry = new PluginEntry();
-                        entry.setKey(in.readUTF());
-                        entry.setClassName(in.readUTF());
-                        entry.setName(in.readUTF());
-                        entry.setPrintable(in.readBoolean());
-                        entry.setDefer(in.readBoolean());
-                        entry.setCategory(category);
-                        m.putIfAbsent(entry.getKey(), entry);
-                    }
+            loadCacheFile(url.openStream());
+        }
+    }
+
+    public void loadCacheFile(final InputStream is) throws IOException {
+        final DataInputStream in = new DataInputStream(new BufferedInputStream(is));
+        try {
+            final int count = in.readInt();
+            for (int i = 0; i < count; i++) {
+                final String category = in.readUTF();
+                final ConcurrentMap<String, PluginEntry> m = pluginCategories.getCategory(category);
+                final int entries = in.readInt();
+                for (int j = 0; j < entries; j++) {
+                    final PluginEntry entry = new PluginEntry();
+                    entry.setKey(in.readUTF());
+                    entry.setClassName(in.readUTF());
+                    entry.setName(in.readUTF());
+                    entry.setPrintable(in.readBoolean());
+                    entry.setDefer(in.readBoolean());
+                    entry.setCategory(category);
+                    m.putIfAbsent(entry.getKey(), entry);
                 }
-            } finally {
-                in.close();
             }
+        } finally {
+            Closer.closeSilent(in);
+            Closer.closeSilent(is);
         }
     }
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformer.java
new file mode 100644
index 0000000..aa604d2
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.logging.log4j.core.config.plugins.processor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import org.apache.maven.plugins.shade.relocation.Relocator;
+import org.apache.maven.plugins.shade.resource.ResourceTransformer;
+import org.codehaus.plexus.util.IOUtil;
+
+/**
+ */
+public final class ShadedResourceTransformer implements ResourceTransformer {
+
+    private final PluginCache pluginCache = new PluginCache();
+    private final String outputResource;
+
+    public ShadedResourceTransformer() {
+        this.outputResource = PluginProcessor.PLUGIN_CACHE_FILE;
+    }
+
+    ShadedResourceTransformer(final String outputResource) {
+        this.outputResource = outputResource;
+    }
+
+    @Override
+    public boolean canTransformResource(final String resource) {
+        return resource.startsWith(PluginProcessor.PLUGIN_CACHE_FILE);
+    }
+
+    @Override
+    public void processResource(final String resource, final InputStream in, final List<Relocator> list) throws IOException {
+        assert PluginProcessor.PLUGIN_CACHE_FILE.equals(resource);
+        pluginCache.loadCacheFile(in);
+    }
+
+    @Override
+    public boolean hasTransformedResource() {
+        return 0 < pluginCache.size();
+    }
+
+    @Override
+    public void modifyOutputStream(final JarOutputStream stream) throws IOException {
+        ByteArrayOutputStream copy = new ByteArrayOutputStream();
+        pluginCache.writeCache(copy);
+        stream.putNextEntry(new JarEntry(outputResource));
+        IOUtil.copy(new ByteArrayInputStream(copy.toByteArray()), stream);
+    }
+
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformerTest.java
new file mode 100644
index 0000000..b5b9d75
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/ShadedResourceTransformerTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.logging.log4j.core.config.plugins.processor;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import org.junit.Test;
+
+import static java.util.Collections.EMPTY_LIST;;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+public final class ShadedResourceTransformerTest {
+
+    @Test
+    public void testCanTransformResource() {
+        final ShadedResourceTransformer transformer = new ShadedResourceTransformer();
+        assertFalse(transformer.canTransformResource("META-INF/MANIFEST.MF"));
+        assertTrue(transformer.canTransformResource(PluginProcessor.PLUGIN_CACHE_FILE));
+    }
+
+    @Test
+    public void testTransformResource() throws IOException {
+
+        final String outputResource = "META-INF/Log4j2Plugins-test-output.dat";
+        final ShadedResourceTransformer transformer = new ShadedResourceTransformer(outputResource);
+        final PluginCache pluginCache = new PluginCache();
+        int minCategories = 0;
+
+        // find and load test resources
+        final List<URL> resources = new ArrayList<URL>();
+        ClassLoader cl = ShadedResourceTransformerTest.class.getClassLoader();
+        minCategories = load(cl.getResources("META-INF/Log4j2Plugins-test-0.dat"), resources, pluginCache, minCategories);
+        minCategories = load(cl.getResources("META-INF/Log4j2Plugins-test-1.dat"), resources, pluginCache, minCategories);
+
+        // loop through processing all resources
+        for(URL url : resources) {
+            transformer.processResource(PluginProcessor.PLUGIN_CACHE_FILE, url.openStream(), EMPTY_LIST);
+        }
+
+        // write transformed resource to temp jar file
+        assertTrue(transformer.hasTransformedResource());
+        JarOutputStream jos = new JarOutputStream(new FileOutputStream("target/test-shaded.jar"));
+        transformer.modifyOutputStream(jos);
+        jos.closeEntry();
+        jos.close();
+
+        // read transformed resource and verify
+        JarFile jarFile = new JarFile("target/test-shaded.jar");
+        pluginCache.loadCacheFile(jarFile.getInputStream(jarFile.getJarEntry(outputResource)));
+        assertTrue(minCategories <= pluginCache.size());
+    }
+
+    private static int load(
+            final Enumeration<URL> urls,
+            final List<URL> resources,
+            final PluginCache pluginCache,
+            final int minCategories) throws IOException {
+
+        assertTrue(urls.hasMoreElements());
+        resources.add(urls.nextElement());
+        assertFalse(urls.hasMoreElements());
+        pluginCache.loadCacheFiles(urls);
+        return Math.max(minCategories, pluginCache.size());
+    }
+
+}
diff --git a/log4j-core/src/test/resources/META-INF/Log4j2Plugins-test-0.dat b/log4j-core/src/test/resources/META-INF/Log4j2Plugins-test-0.dat
new file mode 100644
index 0000000000000000000000000000000000000000..6c7b2d72831ec9cfe00f840908daa2a426f7b6c6
GIT binary patch
literal 10155
zcmb_iZFAhV5r%77mL+-WjvY&yu|4CNd=1;CPSTgOjb5xcs^qA5O4@wnk<f_}UoHeG
z`}*tqEU@6643eN<J|waL?1RN(_t{-gLWnzJB`QiOg!oRZsH~caKNaOP2xnn*A%nD-
zPLq5}KVMu1v=B2hna8pW##p?|zlb$Ed<&3eQCCvD7>GlwX^)YXx}$&Gx+PXs5nahj
zd^Heete3Ycv{U4&3d^dVRb?`r$`aeniZqGl;?+R(vPe@>^2dB^kj@&UQ-ieAIR%rf
zUsPF|hI1TI{JwunvnqH6fny`EYK8!_rnOF_dT+h=i)>b?q>_%_Yg&I`0{?-6ZNfD9
zpNv^qkM&vEaU<rays732-otQz9CY@v7IC2NvZsiD^rKi;43<3$m)GhWn^{<1$?`l&
zD_IJUer8p0qD2mk$f_BH^)^B^&m#&MAbAX@mj=5eaI6LPi~Ky9GKLuf<f^fr7p;j&
zZMoKiu0};#XSw*>K;y-e;4+>>!M+w_^;XSqbl6f(<y$t}ANn!bY$K9zthRO@CTVdE
zlYP^V(p>8y3$z|{?Mlv7MU3V-XfCTjsWkYPoR4X}IbmIfRZ`?)%?{(J_w;3zMNx_G
zJSx(Cy0`lI2(so*8H@&i{M+iHl<J~LV>YCRGue=5R?0EvgEGwHA`7EPDuvZj{L`bc
z^_-Cw+|`1Bm955o9MAJGOQLtx_L!R0J}|=F+TVNIkYv!WHuKJZTR8Hr*-iQK4=dFW
zBMwarV8wAeYnK^3X_iDjS86;H0>M$H&Pb@$fa|len8~st3imKXqzNH7Z3Ir!8o5|3
zK|sgfVOcIC^4aq+f*;Stalh#rWo>{T8o3i$T@*2*fZ@hPT}DzoSZrWMu<32Yjrh8M
z8yj9@l>!sgW+Ur1Mu2g*ly6Gpqv2$3)(0c=v(>zGW53q2_6C1r1irC<lTa(<3j9P4
zu8H78?J!%hJP+%%qB?@ePE{ZpIh~8cels?T+5+Uj;EWh&Y;m?aCSd9vg@ir{m5e{S
z{22pjkC2=)VyANo8mwsY{I>t7a5T6{u$&H;iBn^c<D@!YD)Hrez$p%%Mp%ePxPm&W
z>QcsWScQfT#9#VB&8h%aXVC+Q;?jvZ7KOkaoiKa^)LgBu0$QDKr%JL<baQ7w>bSYi
zDOmgV<t+3Wr8x*O$}9|yW?}1THtfPuo(oUOz|LTCMv&N8`URS8a75rrr1)$9wb_l)
zW9Tv-H`X4en|9?FAEx6Cc_{gqGG!R!G?jc?W#P;?kT0Q_RRLwnkSLjiPr@10zUgRd
zoKEKAi+%&JIXRik(MXuIGN*uIYVt8UV=&L`#$d53LBk-{!^OW*vQpLUPjA-HH;QzK
zt?K@dnvoSMKYf;JU~D!!g3xYTS9;IZnaX$22GbUrmOYjrgxF&Lb1JT(jLyC|sPI}R
zic%&c|ImNcjrP_eL{TPn!;}2Uef?4uIk#)WTGb;6IL+Fsp=n=7#ZI+M`hrE_0$c2+
zD3`cg;I@6Jjah1`isJHtoA&#>!2F6GHzqZ5Ge@b1qc}?UNWuxt%?#N-i9%Jyxe|Zw
zzcVV9PVD-FTy+N515n6KOAM{=ttCJs9uurn(qRjD9DprA3B)k~3cOhwqJs1kinFi+
zspY8a&>fD9OeXV+Dnt!{TiG<ube_mGRz8>F-oUujp%K|!{tbZl7F6u%H28>Z?0cV8
z=V0;FBdpe`Ij$2go9J7w!?c#OFe!a+M0haMjqA@Oa7qH3=vSNHaKO6=EL9*gdaU2D
z1-OCMw^OHG<!!1soGnU2oC^RU?w9A$S1-PN@m^*d85zfH>!`$3Wg7m!`-6-c{xgzj
zY5CU|6ENU?T)f0_D9a(o3Go}TO+z4JT#<I!#jNK+%DOmWM1qZRV_F_2XY}pP8E{~N
z^p6)MXH(B{?WR8%ac9r1t_h^@v?#9X84kUMUT>~^N!Tn8@N8GfjB5-~_OU_H2(X`u
zeTAgOxlvs+3W-CrNu9^=YEPNYKqF~V=AIT}APa#4gYME7Y79l!#sEf)>XST928thH
zsTJ2^sH~&vt?vzhH;1Ee1;`j$77$=D;o&ca0DoCo5`wJEuYKgevM=-Nq%3kGWs5<=
zV9`Yc2=QDzN0myS24WPw#hY-Fx`Fb2e~4P2eaS#5fDMr2z0dtigm^*$n--CN(qrs}
z%6r3fq0l^L3`dP8y|effqN+LE)$^=pk3FWv3szuZpuD-Dml99WV^MY|^tcd#;(+!#
z%od2B|3~969B`+ll=&yX{a)iRoDk2H#4Ai5$-K@cEzET=%<^MKd96_#+duQngDs##
z5qqDQtl?hLYJ4YfcCt7^e3r^<nX<sbl|1jCc7tOQcF;d-AI1wsYp(IMx^}PDJoBh^
z6c?81$G^@{8F*pl39&O+o_Xd$snI;l#miyt(c|a)1Y^L61?m)!ZXd}4!00^Klf%AQ
zA)eyXp;TZ)y!jPwBHtf&-eZ7_Er{bqPy44)5oX~O3?BME9a;zKG4BZ@HIPC)L{?$R
zMOb*UwA~50$vMu$wm~q~1TDqYCt$2Sjl*yVwYpAXNB@DL_7h~C13YRSMb5lPrWYyx
zt9(tNHP0(6u*eKcAb8&GPPd-s(C*NkW_P-$n#V22Y@T=CrCss5b^2~C{1{udje{K~
z=ypB<JM0mR0fY6TG*k*-wGdhFRP_U`w@(0+XPu+$bWg#0rl)mw9cVq%&^lW_?ifeG
zhlg%4kjQ&te*_c_g%g-h=O20k^A+7aLB}{4%)TfLyqI@i6c+K*AQ5rJk#mBf#1%)5
z`VUW92C+^4;oH1!L44RYh-NXvcVDoxbE1LG;)xM=PPNrFf#gQtcDgBOU~;1m;@w>H
zw0|0v-drNZzYzobiyr^}#Q<4Aln<1=Kel|J<lJ38^v*(qQl@BOt|dLpIX647jTtcp
zz12C_`=oak=@B`7@d?*{Xa=W<?Rl7`t+O7zj7&h{!7un9f^)G0f07p4oijpg(>wJ?
n>`dYTJzcl&+&P0F!=EX%ZtVH%5F-;nI=~+;v~IiAH6i{F6xL76

literal 0
HcmV?d00001

diff --git a/log4j-core/src/test/resources/META-INF/Log4j2Plugins-test-1.dat b/log4j-core/src/test/resources/META-INF/Log4j2Plugins-test-1.dat
new file mode 100644
index 0000000000000000000000000000000000000000..0909a1f39ee94822756991d3f3c4a9d069db5127
GIT binary patch
literal 1052
zcmb`G+e!m55Qe9?uC1jP;wyM5K~NCAas`XRs>Q<t#NFvOnkGw<w(i?I*=#*Pw-oGE
zlF9tv{4*o~ume%14FbR(^xwD@ndFt8IXKa#V7%nn1ew%DAynblvv=muIu^3nwO6D;
z5r<d@;BaQqHX>e@sB$!LxgNO8W3Qx5L&0RJ?JjQ#e&FjHTo~c7)KX*>T(zQoZ`4Jw
zz#0{MA;ME2i~?cMT!kZ5>5jROT4f05t?+sf-|!&`9!ap$bUekWHq{zf?KIPBEDCm~
z?(6g<pQCZY()9NE&mQO`7re6GBV26gFR3No<w}Q<6xPA%hOm^9S7ZE8=ru(UE81ZW
zx2?$EEthgePt3B?=nSpZh7I`#j_T;qLm<V?3ee|Dy9t^x<v{Z90?<za&(|dLJ?n&&
v+k*F=J*`1U+oDDda983SC7JHP06(4oYk3GAx6Y-0xHnC5>h%@|f6f43{<m*7

literal 0
HcmV?d00001

-- 
1.9.2

