Skip to content

Commit 41b503e

Browse files
committed
Add a tool to manipulate plugin descriptors
The tool is based on picocli and supports the following commands: * `toJson`: converts a `Log4j2Plugins.dat` to a JSON representation. * `fromJson`: converts the JSON representation of a plugin descriptor to its `Log4j2Plugins.dat` form. * `filterReflectConfig`: filters a GraalVM `reflect-config.json` file by removing the classes that are not contained in a `Log4j2Plugins.json` file.
1 parent 366fd5a commit 41b503e

File tree

12 files changed

+1020
-67
lines changed

12 files changed

+1020
-67
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to you under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
This file activates the `picocli` profile

log4j-codegen/pom.xml

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,10 @@
6161
<scope>provided</scope>
6262
</dependency>
6363

64-
<!-- Compile dependencies: the artifact is shaded, so limit these to the maximum -->
64+
<!-- Compile dependencies: the artifact is shaded, so limit these. -->
6565
<dependency>
6666
<groupId>info.picocli</groupId>
6767
<artifactId>picocli</artifactId>
68-
<version>${picocli.version}</version>
6968
</dependency>
7069

7170
<!-- Test dependencies -->
@@ -89,69 +88,4 @@
8988

9089
</dependencies>
9190

92-
<build>
93-
94-
<plugins>
95-
<plugin>
96-
<groupId>org.apache.maven.plugins</groupId>
97-
<artifactId>maven-compiler-plugin</artifactId>
98-
<configuration>
99-
<annotationProcessorPaths combine.children="append">
100-
<path>
101-
<groupId>info.picocli</groupId>
102-
<artifactId>picocli-codegen</artifactId>
103-
<version>${picocli.version}</version>
104-
</path>
105-
</annotationProcessorPaths>
106-
</configuration>
107-
</plugin>
108-
109-
<plugin>
110-
<groupId>org.apache.maven.plugins</groupId>
111-
<artifactId>maven-shade-plugin</artifactId>
112-
<version>${maven-shade-plugin.version}</version>
113-
<dependencies>
114-
<dependency>
115-
<groupId>org.apache.logging.log4j</groupId>
116-
<artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId>
117-
<version>${project.version}</version>
118-
</dependency>
119-
</dependencies>
120-
<executions>
121-
<execution>
122-
<id>shade-jar-with-dependencies</id>
123-
<goals>
124-
<goal>shade</goal>
125-
</goals>
126-
<configuration>
127-
<filters>
128-
<filter>
129-
<artifact>*:*</artifact>
130-
<excludes>
131-
<exclude>module-info.class</exclude>
132-
<exclude>META-INF/versions/*/module-info.class</exclude>
133-
<exclude>META-INF/MANIFEST.MF</exclude>
134-
</excludes>
135-
</filter>
136-
</filters>
137-
<shadedArtifactAttached>true</shadedArtifactAttached>
138-
<shadedClassifierName>shaded</shadedClassifierName>
139-
<transformers>
140-
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer" />
141-
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer" />
142-
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
143-
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
144-
<manifestEntries>
145-
<Multi-Release>true</Multi-Release>
146-
</manifestEntries>
147-
</transformer>
148-
</transformers>
149-
</configuration>
150-
</execution>
151-
</executions>
152-
</plugin>
153-
</plugins>
154-
155-
</build>
156-
15791
</project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to you under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
This file activates the `picocli` profile
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Licensed to the Apache Software Foundation (ASF) under one or more
4+
~ contributor license agreements. See the NOTICE file distributed with
5+
~ this work for additional information regarding copyright ownership.
6+
~ The ASF licenses this file to you under the Apache License, Version 2.0
7+
~ (the "License"); you may not use this file except in compliance with
8+
~ the License. You may obtain a copy of the License at
9+
~
10+
~ http://www.apache.org/licenses/LICENSE-2.0
11+
~
12+
~ Unless required by applicable law or agreed to in writing, software
13+
~ distributed under the License is distributed on an "AS IS" BASIS,
14+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
~ See the License for the specific language governing permissions and
16+
~ limitations under the License.
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
19+
<modelVersion>4.0.0</modelVersion>
20+
<parent>
21+
<groupId>org.apache.logging.log4j</groupId>
22+
<artifactId>log4j-transform-parent</artifactId>
23+
<version>${revision}</version>
24+
<relativePath>../log4j-transform-parent</relativePath>
25+
</parent>
26+
27+
<artifactId>log4j-converter-plugins-cache</artifactId>
28+
<name>Apache Log4j plugins cache to JSON converter</name>
29+
<description>Converts `Log4j2Plugins.dat` files from and to JSON.</description>
30+
31+
<properties>
32+
<!-- Disabling `bnd-baseline-maven-plugin`, since we don't have a release yet to compare against. -->
33+
<bnd.baseline.fail.on.missing>false</bnd.baseline.fail.on.missing>
34+
35+
<Main-Class>org.apache.logging.log4j.converter.plugins.PluginCacheConverter</Main-Class>
36+
37+
<!-- Dependency versions -->
38+
<jackson.version>2.18.0</jackson.version>
39+
</properties>
40+
41+
<dependencies>
42+
43+
<dependency>
44+
<groupId>org.jspecify</groupId>
45+
<artifactId>jspecify</artifactId>
46+
<scope>provided</scope>
47+
</dependency>
48+
49+
<!-- Compile dependencies: the artifact is shaded, so limit these. -->
50+
<dependency>
51+
<groupId>info.picocli</groupId>
52+
<artifactId>picocli</artifactId>
53+
</dependency>
54+
55+
<dependency>
56+
<groupId>com.fasterxml.jackson.core</groupId>
57+
<artifactId>jackson-core</artifactId>
58+
<version>${jackson.version}</version>
59+
</dependency>
60+
61+
<dependency>
62+
<groupId>com.fasterxml.jackson.core</groupId>
63+
<artifactId>jackson-databind</artifactId>
64+
<version>${jackson.version}</version>
65+
</dependency>
66+
67+
</dependencies>
68+
69+
</project>
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.converter.plugins;
18+
19+
import com.fasterxml.jackson.core.JsonFactory;
20+
import com.fasterxml.jackson.core.JsonGenerator;
21+
import com.fasterxml.jackson.core.JsonParser;
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import java.io.BufferedInputStream;
24+
import java.io.File;
25+
import java.io.IOException;
26+
import java.io.InputStream;
27+
import java.io.OutputStream;
28+
import java.net.MalformedURLException;
29+
import java.net.URL;
30+
import java.net.URLClassLoader;
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
import java.nio.file.Paths;
34+
import java.security.AccessController;
35+
import java.security.PrivilegedAction;
36+
import java.util.Arrays;
37+
import java.util.Collections;
38+
import java.util.HashSet;
39+
import java.util.Set;
40+
import java.util.concurrent.Callable;
41+
import org.apache.logging.log4j.converter.plugins.internal.PluginDescriptors.Namespace;
42+
import org.apache.logging.log4j.converter.plugins.internal.PluginDescriptors.PluginDescriptor;
43+
import org.apache.logging.log4j.converter.plugins.internal.ReflectConfigFilter;
44+
import org.jspecify.annotations.Nullable;
45+
import picocli.CommandLine;
46+
import picocli.CommandLine.Command;
47+
import picocli.CommandLine.Parameters;
48+
49+
@Command(name = "convert")
50+
public class PluginCacheConverter {
51+
52+
private static final JsonFactory jsonFactory = new ObjectMapper().getFactory();
53+
54+
public static void main(final String[] args) {
55+
System.exit(new CommandLine(new PluginCacheConverter()).execute(args));
56+
}
57+
58+
@Command
59+
public void toJson(
60+
@Parameters(description = "Classpath containing Log4j Core plugins") final String classPath,
61+
@Parameters(description = "Output file", defaultValue = "Log4j2Plugins.json") final File outputFile)
62+
throws IOException {
63+
new PluginDescriptorToJsonConverter(classPath, outputFile).call();
64+
}
65+
66+
@Command
67+
public void fromJson(
68+
@Parameters(description = "Input JSON file") final File input,
69+
@Parameters(description = "Output `Log4j2Plugins.dat` file", defaultValue = "Log4j2Plugins.dat")
70+
final File output)
71+
throws IOException {
72+
new JsonToPluginDescriptorConverter(input, output).call();
73+
}
74+
75+
@Command
76+
public void filterReflectConfig(
77+
@Parameters(description = "Plugin descriptor (as JSON)") final File pluginDescriptor,
78+
@Parameters(description = "Reflection configuration") final File reflectConfigInput)
79+
throws IOException {
80+
new ReflectConfigTransformer(pluginDescriptor, reflectConfigInput).call();
81+
}
82+
83+
private static final class PluginDescriptorToJsonConverter implements Callable<@Nullable Void> {
84+
85+
private static final String PLUGIN_CACHE_FILE =
86+
"META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
87+
88+
private final ClassLoader classLoader;
89+
90+
private final File outputFile;
91+
92+
public PluginDescriptorToJsonConverter(final String classPath, final File outputFile) {
93+
final URL[] urls = validateInput(classPath);
94+
this.classLoader =
95+
AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> new URLClassLoader(urls));
96+
this.outputFile = outputFile;
97+
}
98+
99+
@Override
100+
public @Nullable Void call() throws IOException {
101+
try (final OutputStream output = Files.newOutputStream(outputFile.toPath());
102+
final JsonGenerator generator = jsonFactory.createGenerator(output)) {
103+
final PluginDescriptor pluginDescriptor = new PluginDescriptor();
104+
// Read all `Log4j2Plugins.dat` file on the classpath
105+
for (final URL url : Collections.list(classLoader.getResources(PLUGIN_CACHE_FILE))) {
106+
try (final BufferedInputStream input = new BufferedInputStream(url.openStream())) {
107+
pluginDescriptor.readPluginDescriptor(input);
108+
}
109+
}
110+
// Write JSON file
111+
pluginDescriptor.withBuilderHierarchy(classLoader).toJson(generator);
112+
}
113+
return null;
114+
}
115+
116+
private static URL[] validateInput(final String classPath) {
117+
final String[] classPathElements = classPath.split(File.pathSeparator, -1);
118+
return Arrays.stream(classPathElements)
119+
.map(classPathElement -> {
120+
final Path inputPath = Paths.get(classPathElement);
121+
if (!Files.isRegularFile(inputPath)) {
122+
throw new IllegalArgumentException("Input file " + inputPath + " is not a file.");
123+
}
124+
if (!inputPath.getFileName().toString().endsWith(".jar")) {
125+
throw new IllegalArgumentException(
126+
"Invalid input file, only JAR files are supported: " + inputPath);
127+
}
128+
try {
129+
return inputPath.toUri().toURL();
130+
} catch (final MalformedURLException e) {
131+
132+
throw new IllegalArgumentException(
133+
"Innput file " + inputPath + " can not be accessed as URL.", e);
134+
}
135+
})
136+
.toArray(URL[]::new);
137+
}
138+
}
139+
140+
private static class JsonToPluginDescriptorConverter implements Callable<@Nullable Void> {
141+
142+
private final Path input;
143+
private final Path output;
144+
145+
public JsonToPluginDescriptorConverter(final File input, final File output) {
146+
this.input = input.toPath();
147+
this.output = output.toPath();
148+
}
149+
150+
@Override
151+
public @Nullable Void call() throws IOException {
152+
try (final InputStream inputStream = Files.newInputStream(input);
153+
final JsonParser parser = jsonFactory.createParser(inputStream);
154+
final OutputStream outputStream = Files.newOutputStream(output)) {
155+
final PluginDescriptor pluginDescriptor = new PluginDescriptor();
156+
// Input JSON
157+
pluginDescriptor.readJson(parser);
158+
// Output `Log4j2Plugins.dat` file
159+
pluginDescriptor.toPluginDescriptor(outputStream);
160+
}
161+
return null;
162+
}
163+
}
164+
165+
private static class ReflectConfigTransformer implements Callable<@Nullable Void> {
166+
167+
private final Path pluginDescriptorPath;
168+
private final Path input;
169+
private final Path output;
170+
171+
public ReflectConfigTransformer(final File pluginDescriptorPath, final File input) {
172+
this.pluginDescriptorPath = pluginDescriptorPath.toPath();
173+
this.input = input.toPath();
174+
this.output = Paths.get("reflect-config.json");
175+
}
176+
177+
@Override
178+
public @Nullable Void call() throws IOException {
179+
final PluginDescriptor pluginDescriptor = new PluginDescriptor();
180+
// Read plugin descriptor
181+
try (final InputStream inputStream = Files.newInputStream(pluginDescriptorPath);
182+
final JsonParser parser = jsonFactory.createParser(inputStream)) {
183+
pluginDescriptor.readJson(parser);
184+
}
185+
// Find all referenced classes
186+
final Set<String> classNames = new HashSet<>();
187+
pluginDescriptor.getNamespaces().flatMap(Namespace::getPlugins).forEach(p -> {
188+
classNames.add(p.getClassName());
189+
classNames.addAll(p.getBuilderHierarchy());
190+
});
191+
final ReflectConfigFilter filter = new ReflectConfigFilter(classNames);
192+
try (final InputStream inputStream = Files.newInputStream(input);
193+
final JsonParser parser = jsonFactory.createParser(inputStream);
194+
final OutputStream outputStream = Files.newOutputStream(output);
195+
final JsonGenerator generator = jsonFactory.createGenerator(outputStream)) {
196+
filter.filter(parser, generator);
197+
}
198+
return null;
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)