diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index aeef1b991..4cfd2722d 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -10,6 +10,7 @@ dependencies { compile 'commons-io:commons-io:2.5' compile 'org.apache.ant:ant:1.9.7' compile 'org.codehaus.plexus:plexus-utils:3.0.24' + compile "org.apache.logging.log4j:log4j-core:2.11.0" testCompile("org.spockframework:spock-core:1.0-groovy-2.4") { exclude module: 'groovy-all' diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformer.groovy new file mode 100644 index 000000000..9bd1cde39 --- /dev/null +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformer.groovy @@ -0,0 +1,117 @@ +/* + * 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 com.github.jengelman.gradle.plugins.shadow.transformers + +import com.github.jengelman.gradle.plugins.shadow.ShadowStats +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext +import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator +import org.apache.commons.io.IOUtils +import org.apache.commons.io.output.CloseShieldOutputStream +import org.apache.logging.log4j.core.config.plugins.processor.PluginCache +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry +import org.apache.tools.zip.ZipEntry +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.file.FileTreeElement + +import static org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE; + +/** + * Modified from the maven equivalent to work with gradle + * + * @author Paul Nelson Baker + * @see LinkedIn + * @see GitHub + * @see edwgiz/maven-shaded-log4j-transformer + * @see PluginsCacheFileTransformer.java + */ +class Log4j2PluginsCacheFileTransformer implements Transformer { + + private final List temporaryFiles; + private final List relocators; + + public Log4j2PluginsCacheFileTransformer() { + temporaryFiles = new ArrayList<>(); + relocators = new ArrayList<>(); + } + + @Override + boolean canTransformResource(FileTreeElement element) { + return PLUGIN_CACHE_FILE == element.name + } + + @Override + void transform(TransformerContext context) { + def inputStream = context.is + def temporaryFile = File.createTempFile("Log4j2Plugins", ".dat") + temporaryFile.deleteOnExit() + temporaryFiles.add(temporaryFile) + IOUtils.copy(inputStream, new FileOutputStream(temporaryFile)) + def contextRelocators = context.relocators + if (contextRelocators != null) { + this.relocators.addAll(contextRelocators) + } + } + + @Override + boolean hasTransformedResource() { + // This functionality matches the original plugin, however, I'm not clear what + // the exact logic is. From what I can tell temporaryFiles should be never be empty + // if anything has been performed. + def hasTransformedMultipleFiles = temporaryFiles.size() > 1 + def hasAtLeastOneFileAndRelocator = !temporaryFiles.isEmpty() && !relocators.isEmpty() + def hasTransformedResources = hasTransformedMultipleFiles || hasAtLeastOneFileAndRelocator + return hasTransformedResources + } + + @Override + void modifyOutputStream(ZipOutputStream zipOutputStream) { + PluginCache pluginCache = new PluginCache() + pluginCache.loadCacheFiles(getUrlEnumeration()) + relocatePlugins(pluginCache) + zipOutputStream.putNextEntry(new ZipEntry(PLUGIN_CACHE_FILE)) + pluginCache.writeCache(new CloseShieldOutputStream(zipOutputStream)) + temporaryFiles.clear() + } + + private Enumeration getUrlEnumeration() { + def urls = temporaryFiles.collect({ it.toURL() }).asList() + return Collections.enumeration(urls) + } + + private void relocatePlugins(PluginCache pluginCache) { + for (Map currentMap : pluginCache.getAllCategories().values()) { + pluginEntryLoop: + for (PluginEntry currentPluginEntry : currentMap.values()) { + String className = currentPluginEntry.getClassName(); + RelocateClassContext relocateClassContext = new RelocateClassContext(className, new ShadowStats()); + for (Relocator currentRelocator : relocators) { + // If we have a relocator that can relocate our current entry... + boolean canRelocateClass = currentRelocator.canRelocateClass(relocateClassContext); + if (canRelocateClass) { + // Then we perform that relocation and update the plugin entry to reflect the new value. + String relocatedClassName = currentRelocator.relocateClass(relocateClassContext); + currentPluginEntry.setClassName(relocatedClassName); + continue pluginEntryLoop; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.groovy new file mode 100644 index 000000000..18373a784 --- /dev/null +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.groovy @@ -0,0 +1,86 @@ +package com.github.jengelman.gradle.plugins.shadow.transformers + +import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator +import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator +import org.apache.tools.zip.ZipOutputStream +import org.junit.Before +import org.junit.Test + +import java.util.zip.ZipEntry +import java.util.zip.ZipFile + +import static java.util.Collections.singletonList +import static org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE +import static org.junit.Assert.assertFalse +import static org.junit.Assert.assertTrue + +/** + * @author Paul Nelson Baker + * @since 2018-08 + * @see GitHub + * @see LinkedIn + */ +//@RunWith(Parameterized.class) +class Log4j2PluginsCacheFileTransformerTest { + + Log4j2PluginsCacheFileTransformer transformer + + @Before + void setUp() { + transformer = new Log4j2PluginsCacheFileTransformer() + } + + @Test + void testShouldNotTransform() { + transformer.transform(new TransformerContext(PLUGIN_CACHE_FILE, getResourceStream(PLUGIN_CACHE_FILE), null)) + assertFalse(transformer.hasTransformedResource()) + } + + @Test + void testShouldTransform() { + List relocators = new ArrayList<>() + relocators.add(new SimpleRelocator(null, null, null, null)) + transformer.transform(new TransformerContext(PLUGIN_CACHE_FILE, getResourceStream(PLUGIN_CACHE_FILE), relocators)) + assertTrue(transformer.hasTransformedResource()) + } + + @Test + void testRelocators() { + testRelocate("org.apache.logging", "new.location.org.apache.logging", "new.location.org.apache.logging") + testRelocate("org.apache.logging", "new.location.org.apache.logging", "org.apache.logging") + } + + void testRelocate(String source, String pattern, String target) throws IOException { + List relocators = singletonList((Relocator) new SimpleRelocator(source, pattern, null, null)) + transformer.transform(new TransformerContext(PLUGIN_CACHE_FILE, getResourceStream(PLUGIN_CACHE_FILE), relocators)) + assertTrue("Transformer didn't transform resources", transformer.hasTransformedResource()) + // Write out to a fake jar file + def testableZipFile = File.createTempFile("testable-zip-file-", ".jar") + def fileOutputStream = new FileOutputStream(testableZipFile) + def bufferedOutputStream = new BufferedOutputStream(fileOutputStream) + def zipOutputStream = new ZipOutputStream(bufferedOutputStream) + transformer.modifyOutputStream(zipOutputStream) + zipOutputStream.close() + bufferedOutputStream.close() + fileOutputStream.close() + // Pull the data back out and make sure it was transformed + ZipFile zipFile = new ZipFile(testableZipFile) + ZipEntry zipFileEntry = zipFile.getEntry(PLUGIN_CACHE_FILE) + InputStream inputStream = zipFile.getInputStream(zipFileEntry) + new Scanner(inputStream).withCloseable { scanner -> + boolean hasAtLeastOneTransform = false + while (scanner.hasNextLine()) { + String nextLine = scanner.nextLine() + if (nextLine.contains(source)) { + hasAtLeastOneTransform = true + assertTrue("Target wasn't included in transform", nextLine.contains(target)) + } + } + assertTrue("There were no transformations inside the file", hasAtLeastOneTransform) + } + } + + InputStream getResourceStream(String resource) { + return this.class.getClassLoader().getResourceAsStream(resource); + } +}