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);
+ }
+}