Skip to content

Commit 32dab63

Browse files
committed
Improve command syntax
1 parent 41b503e commit 32dab63

File tree

4 files changed

+148
-67
lines changed

4 files changed

+148
-67
lines changed

log4j-converter-plugin-descriptor/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
<relativePath>../log4j-transform-parent</relativePath>
2525
</parent>
2626

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>
27+
<artifactId>log4j-converter-plugin-descriptor</artifactId>
28+
<name>Apache Log4j plugin descriptor tools</name>
29+
<description>Tools to manipulate `Log4j2Plugins.dat` plugin descriptors and synchronize them with GraalVM reachability metadata.</description>
3030

3131
<properties>
3232
<!-- Disabling `bnd-baseline-maven-plugin`, since we don't have a release yet to compare against. -->

log4j-converter-plugin-descriptor/src/main/java/org/apache/logging/log4j/converter/plugins/PluginCacheConverter.java

Lines changed: 140 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,42 @@
2626
import java.io.InputStream;
2727
import java.io.OutputStream;
2828
import java.net.MalformedURLException;
29+
import java.net.URI;
30+
import java.net.URISyntaxException;
2931
import java.net.URL;
3032
import java.net.URLClassLoader;
33+
import java.nio.file.FileSystem;
34+
import java.nio.file.FileSystems;
3135
import java.nio.file.Files;
3236
import java.nio.file.Path;
3337
import java.nio.file.Paths;
3438
import java.security.AccessController;
3539
import java.security.PrivilegedAction;
3640
import java.util.Arrays;
41+
import java.util.Collection;
3742
import java.util.Collections;
3843
import java.util.HashSet;
44+
import java.util.List;
3945
import java.util.Set;
4046
import java.util.concurrent.Callable;
47+
import java.util.stream.Collectors;
48+
import java.util.stream.Stream;
4149
import org.apache.logging.log4j.converter.plugins.internal.PluginDescriptors.Namespace;
4250
import org.apache.logging.log4j.converter.plugins.internal.PluginDescriptors.PluginDescriptor;
4351
import org.apache.logging.log4j.converter.plugins.internal.ReflectConfigFilter;
4452
import org.jspecify.annotations.Nullable;
4553
import picocli.CommandLine;
4654
import picocli.CommandLine.Command;
55+
import picocli.CommandLine.Option;
4756
import picocli.CommandLine.Parameters;
4857

49-
@Command(name = "convert")
58+
@Command(name = "convertPlugin")
5059
public class PluginCacheConverter {
5160

61+
private static final String PLUGIN_DESCRIPTOR_FILE =
62+
"META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
63+
private static final String PLUGIN_DESCRIPTOR_JSON_FILE = "Log4j2Plugins.json";
64+
5265
private static final JsonFactory jsonFactory = new ObjectMapper().getFactory();
5366

5467
public static void main(final String[] args) {
@@ -57,106 +70,132 @@ public static void main(final String[] args) {
5770

5871
@Command
5972
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)
73+
@Parameters(description = "Classpath containing Log4j Core plugins") final List<String> classPath,
74+
@Option(names = {"-o", "--outputDirectory"}) final File outputDirectory)
6275
throws IOException {
63-
new PluginDescriptorToJsonConverter(classPath, outputFile).call();
76+
new PluginDescriptorToJsonConverter(classPath, outputDirectory).call();
6477
}
6578

6679
@Command
6780
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)
81+
@Parameters(description = "Input `Log4j2Plugins.json` JSON file") final File input,
82+
@Option(names = {"-o", "--outputDirectory"}) final File outputDirectory)
7183
throws IOException {
72-
new JsonToPluginDescriptorConverter(input, output).call();
84+
new JsonToPluginDescriptorConverter(input, outputDirectory).call();
7385
}
7486

7587
@Command
7688
public void filterReflectConfig(
7789
@Parameters(description = "Plugin descriptor (as JSON)") final File pluginDescriptor,
78-
@Parameters(description = "Reflection configuration") final File reflectConfigInput)
90+
@Parameters(description = "Classpath containing GraalVM descriptors") final List<String> classPath,
91+
@Option(names = {"-o", "--outputDirectory"}) final File outputDirectory)
7992
throws IOException {
80-
new ReflectConfigTransformer(pluginDescriptor, reflectConfigInput).call();
93+
new ReflectConfigTransformer(pluginDescriptor, classPath, outputDirectory).call();
8194
}
8295

83-
private static final class PluginDescriptorToJsonConverter implements Callable<@Nullable Void> {
96+
private static Collection<Path> validateClassPath(final Collection<String> classPath) {
97+
return classPath.stream()
98+
.flatMap(classPathElement -> Arrays.stream(classPathElement.split(File.pathSeparator, -1)))
99+
.map(classPathElement -> {
100+
final Path inputPath = Paths.get(classPathElement);
101+
if (!Files.isRegularFile(inputPath)) {
102+
throw new IllegalArgumentException("Input file " + inputPath + " is not a file.");
103+
}
104+
if (!inputPath.getFileName().toString().endsWith(".jar")) {
105+
throw new IllegalArgumentException(
106+
"Invalid input file, only JAR files are supported: " + inputPath);
107+
}
108+
return inputPath;
109+
})
110+
.collect(Collectors.toList());
111+
}
84112

85-
private static final String PLUGIN_CACHE_FILE =
86-
"META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
113+
private static void createParentDirectories(final Path path) throws IOException {
114+
final Path parent = path.getParent();
115+
if (parent != null) {
116+
Files.createDirectories(parent);
117+
}
118+
}
119+
120+
private static final class PluginDescriptorToJsonConverter implements Callable<@Nullable Void> {
87121

88122
private final ClassLoader classLoader;
89123

90-
private final File outputFile;
124+
private final @Nullable Path outputDirectory;
91125

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;
126+
PluginDescriptorToJsonConverter(final Collection<String> classPath, final @Nullable File outputDirectory) {
127+
this.classLoader = AccessController.doPrivileged(
128+
(PrivilegedAction<ClassLoader>) () -> new URLClassLoader(validateClassPath(classPath).stream()
129+
.map(p -> {
130+
try {
131+
return p.toUri().toURL();
132+
} catch (final MalformedURLException e) {
133+
throw new IllegalArgumentException(e);
134+
}
135+
})
136+
.toArray(URL[]::new)));
137+
this.outputDirectory = outputDirectory != null ? outputDirectory.toPath() : null;
97138
}
98139

99140
@Override
100141
public @Nullable Void call() throws IOException {
101-
try (final OutputStream output = Files.newOutputStream(outputFile.toPath());
102-
final JsonGenerator generator = jsonFactory.createGenerator(output)) {
142+
final OutputStream output;
143+
if (outputDirectory != null) {
144+
Files.createDirectories(outputDirectory);
145+
output = Files.newOutputStream(outputDirectory.resolve(PLUGIN_DESCRIPTOR_JSON_FILE));
146+
} else {
147+
output = System.out;
148+
}
149+
try (final JsonGenerator generator = jsonFactory.createGenerator(output)) {
103150
final PluginDescriptor pluginDescriptor = new PluginDescriptor();
104151
// Read all `Log4j2Plugins.dat` file on the classpath
105-
for (final URL url : Collections.list(classLoader.getResources(PLUGIN_CACHE_FILE))) {
152+
for (final URL url : Collections.list(classLoader.getResources(PLUGIN_DESCRIPTOR_FILE))) {
106153
try (final BufferedInputStream input = new BufferedInputStream(url.openStream())) {
107154
pluginDescriptor.readPluginDescriptor(input);
108155
}
109156
}
110157
// Write JSON file
111158
pluginDescriptor.withBuilderHierarchy(classLoader).toJson(generator);
159+
} finally {
160+
if (output != System.out) {
161+
output.close();
162+
}
112163
}
113164
return null;
114165
}
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-
}
138166
}
139167

140168
private static class JsonToPluginDescriptorConverter implements Callable<@Nullable Void> {
141169

142170
private final Path input;
143-
private final Path output;
171+
private final @Nullable Path outputDirectory;
144172

145-
public JsonToPluginDescriptorConverter(final File input, final File output) {
173+
JsonToPluginDescriptorConverter(final File input, final @Nullable File outputDirectory) {
146174
this.input = input.toPath();
147-
this.output = output.toPath();
175+
this.outputDirectory = outputDirectory != null ? outputDirectory.toPath() : null;
148176
}
149177

150178
@Override
151179
public @Nullable Void call() throws IOException {
180+
final OutputStream output;
181+
if (outputDirectory != null) {
182+
final Path pluginDescriptorPath = outputDirectory.resolve(PLUGIN_DESCRIPTOR_FILE);
183+
createParentDirectories(pluginDescriptorPath);
184+
output = Files.newOutputStream(pluginDescriptorPath);
185+
} else {
186+
output = System.out;
187+
}
152188
try (final InputStream inputStream = Files.newInputStream(input);
153-
final JsonParser parser = jsonFactory.createParser(inputStream);
154-
final OutputStream outputStream = Files.newOutputStream(output)) {
189+
final JsonParser parser = jsonFactory.createParser(inputStream)) {
155190
final PluginDescriptor pluginDescriptor = new PluginDescriptor();
156191
// Input JSON
157192
pluginDescriptor.readJson(parser);
158193
// Output `Log4j2Plugins.dat` file
159-
pluginDescriptor.toPluginDescriptor(outputStream);
194+
pluginDescriptor.toPluginDescriptor(output);
195+
} finally {
196+
if (output != System.out) {
197+
output.close();
198+
}
160199
}
161200
return null;
162201
}
@@ -165,13 +204,56 @@ public JsonToPluginDescriptorConverter(final File input, final File output) {
165204
private static class ReflectConfigTransformer implements Callable<@Nullable Void> {
166205

167206
private final Path pluginDescriptorPath;
168-
private final Path input;
169-
private final Path output;
207+
private final Collection<Path> classPath;
208+
private final @Nullable Path outputDirectory;
170209

171-
public ReflectConfigTransformer(final File pluginDescriptorPath, final File input) {
210+
ReflectConfigTransformer(
211+
final File pluginDescriptorPath,
212+
final Collection<String> classPath,
213+
final @Nullable File outputDirectory) {
172214
this.pluginDescriptorPath = pluginDescriptorPath.toPath();
173-
this.input = input.toPath();
174-
this.output = Paths.get("reflect-config.json");
215+
this.classPath = validateClassPath(classPath);
216+
this.outputDirectory = outputDirectory != null ? outputDirectory.toPath() : null;
217+
}
218+
219+
void filterReflectConfigInJar(final Path jar, final ReflectConfigFilter filter) throws IOException {
220+
final URI jarFileSystemRoot;
221+
try {
222+
jarFileSystemRoot = new URI("jar", jar.toUri().toASCIIString(), null);
223+
} catch (final URISyntaxException e) {
224+
throw new IllegalArgumentException(e);
225+
}
226+
try (final FileSystem fileSystem = FileSystems.newFileSystem(jarFileSystemRoot, Collections.emptyMap())) {
227+
final Path rootPath = fileSystem.getPath("/");
228+
final Path nativeImagePath = rootPath.resolve("META-INF/native-image");
229+
try (final Stream<Path> paths = Files.walk(nativeImagePath, 3)) {
230+
paths.filter(p ->
231+
"reflect-config.json".equals(p.getFileName().toString()))
232+
.forEach(p -> filterReflectConfig(rootPath, p, filter));
233+
}
234+
}
235+
}
236+
237+
void filterReflectConfig(final Path rootPath, final Path reflectConfig, final ReflectConfigFilter filter) {
238+
try {
239+
final OutputStream output;
240+
if (outputDirectory != null) {
241+
final String relativePath =
242+
rootPath.relativize(reflectConfig).toString();
243+
final Path outputPath = outputDirectory.resolve(relativePath);
244+
createParentDirectories(outputPath);
245+
output = Files.newOutputStream(outputPath);
246+
} else {
247+
output = System.out;
248+
}
249+
try (final InputStream inputStream = Files.newInputStream(reflectConfig);
250+
final JsonParser parser = jsonFactory.createParser(inputStream);
251+
final JsonGenerator generator = jsonFactory.createGenerator(output)) {
252+
filter.filter(parser, generator);
253+
}
254+
} catch (final IOException e) {
255+
throw new RuntimeException(e);
256+
}
175257
}
176258

177259
@Override
@@ -189,11 +271,8 @@ public ReflectConfigTransformer(final File pluginDescriptorPath, final File inpu
189271
classNames.addAll(p.getBuilderHierarchy());
190272
});
191273
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);
274+
for (final Path jar : classPath) {
275+
filterReflectConfigInJar(jar, filter);
197276
}
198277
return null;
199278
}

log4j-converter-plugin-descriptor/src/main/java/org/apache/logging/log4j/converter/plugins/internal/PluginDescriptors.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,15 @@ private Plugin(
256256
}
257257

258258
public Set<String> getPluginNames() {
259-
return pluginNames;
259+
return Collections.unmodifiableSet(pluginNames);
260260
}
261261

262262
public String getClassName() {
263263
return className;
264264
}
265265

266266
public List<String> getBuilderHierarchy() {
267-
return builderHierarchy;
267+
return Collections.unmodifiableList(builderHierarchy);
268268
}
269269

270270
static Plugin fromDataInput(final DataInput dataInput) throws IOException {

log4j-converter-plugin-descriptor/src/main/java/org/apache/logging/log4j/converter/plugins/internal/ReflectConfigFilter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.fasterxml.jackson.core.JsonToken;
2525
import com.fasterxml.jackson.databind.JsonNode;
2626
import java.io.IOException;
27+
import java.util.HashSet;
2728
import java.util.Set;
2829

2930
public class ReflectConfigFilter {
@@ -33,7 +34,7 @@ public class ReflectConfigFilter {
3334
private final Set<String> includeClassNames;
3435

3536
public ReflectConfigFilter(final Set<String> includeClassNames) {
36-
this.includeClassNames = includeClassNames;
37+
this.includeClassNames = new HashSet<>(includeClassNames);
3738
}
3839

3940
public void filter(final JsonParser input, final JsonGenerator output) throws IOException {
@@ -47,6 +48,7 @@ public void filter(final JsonParser input, final JsonGenerator output) throws IO
4748
final String name = nameNode.asText();
4849
// Include all the plugin visitors
4950
if (name.startsWith("org.apache.logging.log4j.core.config.plugins.visitors")
51+
|| name.startsWith("org.apache.logging.log4j.core.config.plugins.validation.validators")
5052
|| includeClassNames.contains(name)) {
5153
output.writeTree(node);
5254
}

0 commit comments

Comments
 (0)