This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new f93b6b19db fix: Make `GraalVmProcessor` Arguments Optional (#3772)
f93b6b19db is described below

commit f93b6b19db61bba0b8b88bb78652295368d396dc
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sat Jun 28 09:23:18 2025 +0200

    fix: Make `GraalVmProcessor` Arguments Optional (#3772)
    
    Fixes #3771
    
    This PR makes the `-Alog4j.graalvm.groupId` and 
`-Alog4j.graalvm.artifactId` arguments optional.
    
    * If **no arguments** are provided, metadata is stored in:
    
      ```
      META-INF/native-image/log4j-generated/<content-derived-value>
      ```
      Previously an error was thrown.
    
    * If **arguments are provided**, files go to:
    
      ```
      META-INF/native-image/log4j-generated/<groupId>/<artifactId>
      ```
      Previously `META-INF/native-image/<groupId>/<artifactId>` was used. The 
new path prevents collisions with user-provided metadata.
    
    Co-authored-by: Copilot <[email protected]>
---
 .../plugins/processor/GraalVmProcessorTest.java    | 188 +++++++++++++++++----
 .../GraalVmProcessorTest/java/FakeAnnotations.java |  83 +++++++++
 .../GraalVmProcessorTest/java/FakePlugin.java      |  90 ++++++++++
 .../config/plugins/processor/GraalVmProcessor.java |  58 +++++--
 src/changelog/.2.x.x/3771_graalvm-params.xml       |  10 ++
 5 files changed, 379 insertions(+), 50 deletions(-)

diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java
index 9f8b51a7b7..9bdbcad8dd 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java
@@ -16,31 +16,56 @@
  */
 package org.apache.logging.log4j.core.config.plugins.processor;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
 import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
 import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.URL;
-import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import org.apache.commons.io.IOUtils;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.util.Elements;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import org.jspecify.annotations.Nullable;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.CleanupMode;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
 
 class GraalVmProcessorTest {
 
+    private static final String FAKE_PLUGIN_NAME = "example.FakePlugin";
     private static final Object FAKE_PLUGIN = asMap(
             "name",
-            FakePlugin.class.getName(),
+            FAKE_PLUGIN_NAME,
             "methods",
             asList(
                     asMap("name", "<init>", "parameterTypes", emptyList()),
@@ -57,9 +82,10 @@ class GraalVmProcessorTest {
                                     "java.lang.String"))),
             "fields",
             emptyList());
+    private static final String FAKE_PLUGIN_BUILDER_NAME = FAKE_PLUGIN_NAME + 
"$Builder";
     private static final Object FAKE_PLUGIN_BUILDER = asMap(
             "name",
-            FakePlugin.Builder.class.getName(),
+            FAKE_PLUGIN_BUILDER_NAME,
             "methods",
             emptyList(),
             "fields",
@@ -71,21 +97,27 @@ class GraalVmProcessorTest {
                     asMap("name", "loggerContext"),
                     asMap("name", "node"),
                     asMap("name", "value")));
-    private static final Object FAKE_PLUGIN_NESTED = 
onlyNoArgsConstructor(FakePlugin.Nested.class);
-    private static final Object FAKE_CONSTRAINT_VALIDATOR =
-            
onlyNoArgsConstructor(FakeAnnotations.FakeConstraintValidator.class);
-    private static final Object FAKE_PLUGIN_VISITOR = 
onlyNoArgsConstructor(FakeAnnotations.FakePluginVisitor.class);
+    private static final String FAKE_PLUGIN_NESTED_NAME = FAKE_PLUGIN_NAME + 
"$Nested";
+    private static final Object FAKE_PLUGIN_NESTED = 
onlyNoArgsConstructor(FAKE_PLUGIN_NESTED_NAME);
+    private static final String FAKE_CONSTRAINT_VALIDATOR_NAME = 
"example.FakeAnnotations$FakeConstraintValidator";
+    private static final Object FAKE_CONSTRAINT_VALIDATOR = 
onlyNoArgsConstructor(FAKE_CONSTRAINT_VALIDATOR_NAME);
+    private static final String FAKE_PLUGIN_VISITOR_NAME = 
"example.FakeAnnotations$FakePluginVisitor";
+    private static final Object FAKE_PLUGIN_VISITOR = 
onlyNoArgsConstructor(FAKE_PLUGIN_VISITOR_NAME);
+
+    private static final String GROUP_ID = "groupId";
+    private static final String ARTIFACT_ID = "artifactId";
+    private static final String FALLBACK_METADATA_FOLDER = "fooBar";
 
     /**
      * Generates a metadata element with just a single no-arg constructor.
      *
-     * @param clazz The name of the metadata element.
+     * @param className The name of the metadata element.
      * @return A GraalVM metadata element.
      */
-    private static Object onlyNoArgsConstructor(Class<?> clazz) {
+    private static Object onlyNoArgsConstructor(String className) {
         return asMap(
                 "name",
-                clazz.getName(),
+                className,
                 "methods",
                 singletonList(asMap("name", "<init>", "parameterTypes", 
emptyList())),
                 "fields",
@@ -103,43 +135,131 @@ class GraalVmProcessorTest {
         return map;
     }
 
-    private static String reachabilityMetadata;
+    private static Path sourceDir;
+
+    @TempDir
+    private static Path outputDir;
 
     @BeforeAll
-    static void setup() throws IOException {
-        // There are two descriptors, choose the one in `test-classes`
-        URL reachabilityMetadataUrl = null;
-        for (URL url : Collections.list(GraalVmProcessor.class
-                .getClassLoader()
-                
.getResources("META-INF/native-image/org.apache.logging.log4j/log4j-core-test/reflect-config.json")))
 {
-            if (url.getPath().contains("test-classes")) {
-                reachabilityMetadataUrl = url;
-                break;
-            }
-        }
-        assertThat(reachabilityMetadataUrl).isNotNull();
-        reachabilityMetadata = IOUtils.toString(reachabilityMetadataUrl, 
StandardCharsets.UTF_8);
+    static void setup() throws Exception {
+        URL sourceUrl = 
requireNonNull(GraalVmProcessorTest.class.getResource("/GraalVmProcessorTest/java"));
+        sourceDir = Paths.get(sourceUrl.toURI());
+        // Generate metadata
+        List<String> diagnostics = generateDescriptor(sourceDir, GROUP_ID, 
ARTIFACT_ID, outputDir);
+        assertThat(diagnostics).isEmpty();
     }
 
     static Stream<Arguments> containsSpecificEntries() {
         return Stream.of(
-                Arguments.of(FakePlugin.class, FAKE_PLUGIN),
-                Arguments.of(FakePlugin.Builder.class, FAKE_PLUGIN_BUILDER),
-                Arguments.of(FakePlugin.Nested.class, FAKE_PLUGIN_NESTED),
-                Arguments.of(FakeAnnotations.FakeConstraintValidator.class, 
FAKE_CONSTRAINT_VALIDATOR),
-                Arguments.of(FakeAnnotations.FakePluginVisitor.class, 
FAKE_PLUGIN_VISITOR));
+                Arguments.of(FAKE_PLUGIN_NAME, FAKE_PLUGIN),
+                Arguments.of(FAKE_PLUGIN_BUILDER_NAME, FAKE_PLUGIN_BUILDER),
+                Arguments.of(FAKE_PLUGIN_NESTED_NAME, FAKE_PLUGIN_NESTED),
+                Arguments.of(FAKE_CONSTRAINT_VALIDATOR_NAME, 
FAKE_CONSTRAINT_VALIDATOR),
+                Arguments.of(FAKE_PLUGIN_VISITOR_NAME, FAKE_PLUGIN_VISITOR));
     }
 
     @ParameterizedTest
     @MethodSource
-    void containsSpecificEntries(Class<?> clazz, Object expectedJson) {
+    void containsSpecificEntries(String className, Object expectedJson) throws 
IOException {
+        // Read metadata
+        Path reachabilityMetadataPath =
+                
outputDir.resolve("META-INF/native-image/log4j-generated/groupId/artifactId/reflect-config.json");
+        String reachabilityMetadata = new 
String(Files.readAllBytes(reachabilityMetadataPath), UTF_8);
         assertThatJson(reachabilityMetadata)
-                .inPath(filterByName(clazz))
+                .inPath(String.format("$[?(@.name == '%s')]", className))
                 .isArray()
                 .contains(json(expectedJson));
     }
 
-    private String filterByName(Class<?> clazz) {
-        return String.format("$[?(@.name == '%s')]", clazz.getName());
+    static Stream<Arguments> reachabilityMetadataPath() {
+        return Stream.of(
+                Arguments.of(
+                        "groupId",
+                        "artifactId",
+                        
"META-INF/native-image/log4j-generated/groupId/artifactId/reflect-config.json"),
+                Arguments.of(null, "artifactId", 
"META-INF/native-image/log4j-generated/fooBar/reflect-config.json"),
+                Arguments.of("groupId", null, 
"META-INF/native-image/log4j-generated/fooBar/reflect-config.json"),
+                Arguments.of(null, null, 
"META-INF/native-image/log4j-generated/fooBar/reflect-config.json"));
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void reachabilityMetadataPath(@Nullable String groupId, @Nullable String 
artifactId, String expected) {
+        Messager messager = Mockito.mock(Messager.class);
+        Elements elements = Mockito.mock(Elements.class);
+        ProcessingEnvironment processingEnv = 
Mockito.mock(ProcessingEnvironment.class);
+        when(processingEnv.getMessager()).thenReturn(messager);
+        when(processingEnv.getElementUtils()).thenReturn(elements);
+        GraalVmProcessor processor = new GraalVmProcessor();
+        processor.init(processingEnv);
+        assertThat(processor.getReachabilityMetadataPath(groupId, artifactId, 
FALLBACK_METADATA_FOLDER))
+                .isEqualTo(expected);
+    }
+
+    @Test
+    void whenNoGroupIdAndArtifactId_thenWarningIsPrinted(@TempDir(cleanup = 
CleanupMode.NEVER) Path outputDir)
+            throws Exception {
+        List<String> diagnostics = generateDescriptor(sourceDir, null, null, 
outputDir);
+        assertThat(diagnostics).hasSize(1);
+        // The warning message should contain the information about the 
missing groupId and artifactId arguments
+        assertThat(diagnostics.get(0))
+                .contains(
+                        "recommended",
+                        "-A" + GraalVmProcessor.GROUP_ID + "=<groupId>",
+                        "-A" + GraalVmProcessor.ARTIFACT_ID + "=<artifactId>");
+        Path path = outputDir.resolve("META-INF/native-image/log4j-generated");
+        List<Path> reachabilityMetadataFolders;
+        try (Stream<Path> files = Files.list(path)) {
+            reachabilityMetadataFolders = 
files.filter(Files::isDirectory).collect(Collectors.toList());
+        }
+        // The generated folder name should be deterministic and based solely 
on the descriptor content.
+        // If the descriptor changes, this test and the expected folder name 
must be updated accordingly.
+        
assertThat(reachabilityMetadataFolders).hasSize(1).containsExactly(path.resolve("62162090"));
+        
assertThat(reachabilityMetadataFolders.get(0).resolve("reflect-config.json"))
+                .as("Reachability metadata file")
+                .exists();
+    }
+
+    private static List<String> generateDescriptor(
+            Path sourceDir, @Nullable String groupId, @Nullable String 
artifactId, Path outputDir) throws Exception {
+        // Instantiate the tooling
+        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        final StandardJavaFileManager fileManager = 
compiler.getStandardFileManager(null, Locale.ROOT, UTF_8);
+
+        // Populate sources
+        final Iterable<? extends JavaFileObject> sources;
+        try (final Stream<Path> files = Files.walk(sourceDir)) {
+            File[] sourceFiles =
+                    
files.filter(Files::isRegularFile).map(Path::toFile).toArray(File[]::new);
+            sources = fileManager.getJavaFileObjects(sourceFiles);
+        }
+
+        // Set the target path used by `DescriptorGenerator` to dump the 
generated files
+        fileManager.setLocation(StandardLocation.CLASS_OUTPUT, 
Collections.singleton(outputDir.toFile()));
+
+        // Prepare the compiler options
+        List<String> options = new ArrayList<>();
+        options.add("-proc:only");
+        options.add("-processor");
+        options.add(GraalVmProcessor.class.getName());
+        if (groupId != null) {
+            options.add("-A" + GraalVmProcessor.GROUP_ID + "=" + groupId);
+        }
+        if (artifactId != null) {
+            options.add("-A" + GraalVmProcessor.ARTIFACT_ID + "=" + 
artifactId);
+        }
+
+        // Compile the sources
+        final Path descriptorFilePath = outputDir.resolve("plugins.xml");
+        final DiagnosticCollector<JavaFileObject> diagnosticCollector = new 
DiagnosticCollector<>();
+        final JavaCompiler.CompilationTask task =
+                compiler.getTask(null, fileManager, diagnosticCollector, 
options, null, sources);
+        task.call();
+
+        // Verify successful compilation
+        return diagnosticCollector.getDiagnostics().stream()
+                .filter(d -> d.getKind() != Diagnostic.Kind.NOTE)
+                .map(d -> d.getMessage(Locale.ROOT))
+                .collect(Collectors.toList());
     }
 }
diff --git 
a/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeAnnotations.java
 
b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeAnnotations.java
new file mode 100644
index 0000000000..c3223781f4
--- /dev/null
+++ 
b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeAnnotations.java
@@ -0,0 +1,83 @@
+/*
+ * 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 example;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.PluginVisitorStrategy;
+import org.apache.logging.log4j.core.config.plugins.validation.Constraint;
+import 
org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+
+/**
+ * Fake constraint and plugin visitor that are accessed through reflection.
+ */
+public class FakeAnnotations {
+
+    @Constraint(FakeConstraintValidator.class)
+    public @interface FakeConstraint {}
+
+    public static class FakeConstraintValidator implements 
ConstraintValidator<FakeConstraint> {
+        @Override
+        public void initialize(FakeConstraint annotation) {}
+
+        @Override
+        public boolean isValid(String name, Object value) {
+            return false;
+        }
+    }
+
+    @PluginVisitorStrategy(FakePluginVisitor.class)
+    public @interface FakeAnnotation {}
+
+    public static class FakePluginVisitor implements 
PluginVisitor<FakeAnnotation> {
+
+        @Override
+        public PluginVisitor<FakeAnnotation> setAnnotation(Annotation 
annotation) {
+            return null;
+        }
+
+        @Override
+        public PluginVisitor<FakeAnnotation> setAliases(String... aliases) {
+            return null;
+        }
+
+        @Override
+        public PluginVisitor<FakeAnnotation> setStrSubstitutor(StrSubstitutor 
substitutor) {
+            return null;
+        }
+
+        @Override
+        public PluginVisitor<FakeAnnotation> setMember(Member member) {
+            return null;
+        }
+
+        @Override
+        public Object visit(Configuration configuration, Node node, LogEvent 
event, StringBuilder log) {
+            return null;
+        }
+
+        @Override
+        public PluginVisitor<FakeAnnotation> setConversionType(Class<?> 
conversionType) {
+            return null;
+        }
+    }
+}
diff --git 
a/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakePlugin.java 
b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakePlugin.java
new file mode 100644
index 0000000000..90b872c0a3
--- /dev/null
+++ 
b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakePlugin.java
@@ -0,0 +1,90 @@
+/*
+ * 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 example;
+
+import java.io.Serializable;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAliases;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.plugins.PluginLoggerContext;
+import org.apache.logging.log4j.core.config.plugins.PluginNode;
+import org.apache.logging.log4j.core.config.plugins.PluginValue;
+
+/**
+ * Test plugin class for unit tests.
+ */
+@Plugin(name = "Fake", category = "Test")
+@PluginAliases({"AnotherFake", "StillFake"})
+public class FakePlugin {
+
+    @Plugin(name = "Nested", category = "Test")
+    public static class Nested {}
+
+    @PluginFactory
+    public static FakePlugin newPlugin(
+            @PluginAttribute("attribute") int attribute,
+            @PluginElement("layout") Layout<? extends Serializable> layout,
+            @PluginConfiguration Configuration config,
+            @PluginNode Node node,
+            @PluginLoggerContext LoggerContext loggerContext,
+            @PluginValue("value") String value) {
+        return null;
+    }
+
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    public static class Builder implements 
org.apache.logging.log4j.core.util.Builder<FakePlugin> {
+
+        @PluginBuilderAttribute
+        @SuppressWarnings("log4j.public.setter")
+        private int attribute;
+
+        @PluginBuilderAttribute
+        @SuppressWarnings("log4j.public.setter")
+        private int attributeWithoutPublicSetterButWithSuppressAnnotation;
+
+        @PluginElement("layout")
+        private Layout<? extends Serializable> layout;
+
+        @PluginConfiguration
+        private Configuration config;
+
+        @PluginNode
+        private Node node;
+
+        @PluginLoggerContext
+        private LoggerContext loggerContext;
+
+        @PluginValue("value")
+        private String value;
+
+        @Override
+        public FakePlugin build() {
+            return null;
+        }
+    }
+}
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java
index 3b1f9bd5cb..e6178f2a17 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java
@@ -18,9 +18,11 @@ package 
org.apache.logging.log4j.core.config.plugins.processor;
 
 import aQute.bnd.annotation.Resolution;
 import aQute.bnd.annotation.spi.ServiceProvider;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -75,8 +77,10 @@ import org.jspecify.annotations.Nullable;
 @SupportedOptions({"log4j.graalvm.groupId", "log4j.graalvm.artifactId"})
 public class GraalVmProcessor extends AbstractProcessor {
 
-    private static final String GROUP_ID = "log4j.graalvm.groupId";
-    private static final String ARTIFACT_ID = "log4j.graalvm.artifactId";
+    static final String GROUP_ID = "log4j.graalvm.groupId";
+    static final String ARTIFACT_ID = "log4j.graalvm.artifactId";
+    private static final String LOCATION_PREFIX = 
"META-INF/native-image/log4j-generated/";
+    private static final String LOCATION_SUFFIX = "/reflect-config.json";
     private static final String PROCESSOR_NAME = 
GraalVmProcessor.class.getSimpleName();
 
     private final Map<String, ReachabilityMetadata.Type> reachableTypes = new 
HashMap<>();
@@ -184,11 +188,21 @@ public class GraalVmProcessor extends AbstractProcessor {
     }
 
     private void writeReachabilityMetadata() {
-        //
-        // Many users will have `log4j-core` on the annotation processor path, 
but do not have Log4j Plugins.
-        // Therefore, we check for the annotation processor required options 
only if some elements were processed.
-        //
-        String reachabilityMetadataPath = getReachabilityMetadataPath();
+        // Compute the reachability metadata
+        ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
+        try {
+            ReachabilityMetadata.writeReflectConfig(reachableTypes.values(), 
arrayOutputStream);
+        } catch (IOException e) {
+            String message = String.format(
+                    "%s: an error occurred while generating reachability 
metadata: %s", PROCESSOR_NAME, e.getMessage());
+            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
message);
+            return;
+        }
+        byte[] data = arrayOutputStream.toByteArray();
+
+        Map<String, String> options = processingEnv.getOptions();
+        String reachabilityMetadataPath = getReachabilityMetadataPath(
+                options.get(GROUP_ID), options.get(ARTIFACT_ID), 
Integer.toHexString(Arrays.hashCode(data)));
         Messager messager = processingEnv.getMessager();
         messager.printMessage(
                 Diagnostic.Kind.NOTE,
@@ -203,7 +217,7 @@ public class GraalVmProcessor extends AbstractProcessor {
                         reachabilityMetadataPath,
                         processedElements.toArray(new Element[0]))
                 .openOutputStream()) {
-            ReachabilityMetadata.writeReflectConfig(reachableTypes.values(), 
output);
+            output.write(data);
         } catch (IOException e) {
             String message = String.format(
                     "%s: unable to write reachability metadata to file `%s`", 
PROCESSOR_NAME, reachabilityMetadataPath);
@@ -212,18 +226,30 @@ public class GraalVmProcessor extends AbstractProcessor {
         }
     }
 
-    private String getReachabilityMetadataPath() {
-        String groupId = processingEnv.getOptions().get(GROUP_ID);
-        String artifactId = processingEnv.getOptions().get(ARTIFACT_ID);
+    /**
+     * Returns the path to the reachability metadata file.
+     * <p>
+     *     If the groupId or artifactId is not specified, a warning is printed 
and a fallback folder name is used.
+     *     The fallback folder name should be reproducible, but unique enough 
to avoid conflicts.
+     * </p>
+     *
+     * @param groupId The group ID of the plugin.
+     * @param artifactId The artifact ID of the plugin.
+     * @param fallbackFolderName The fallback folder name to use if groupId or 
artifactId is not specified.
+     */
+    String getReachabilityMetadataPath(
+            @Nullable String groupId, @Nullable String artifactId, String 
fallbackFolderName) {
         if (groupId == null || artifactId == null) {
             String message = String.format(
-                    "The `%s` annotation processor is missing the required 
`%s` and `%s` options.%n"
-                            + "The generation of GraalVM reflection metadata 
for your Log4j Plugins will be disabled.",
+                    "The `%1$s` annotation processor is missing the 
recommended `%2$s` and `%3$s` options.%n"
+                            + "To follow the GraalVM recommendations, please 
add the following options to your build tool:%n"
+                            + "  -A%2$s=<groupId>%n"
+                            + "  -A%3$s=<artifactId>%n",
                     PROCESSOR_NAME, GROUP_ID, ARTIFACT_ID);
-            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
message);
-            throw new IllegalArgumentException(message);
+            processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, 
message);
+            return LOCATION_PREFIX + fallbackFolderName + LOCATION_SUFFIX;
         }
-        return 
String.format("META-INF/native-image/%s/%s/reflect-config.json", groupId, 
artifactId);
+        return LOCATION_PREFIX + groupId + '/' + artifactId + LOCATION_SUFFIX;
     }
 
     private void addField(TypeElement parent, VariableElement element) {
diff --git a/src/changelog/.2.x.x/3771_graalvm-params.xml 
b/src/changelog/.2.x.x/3771_graalvm-params.xml
new file mode 100644
index 0000000000..ff3c79b1ef
--- /dev/null
+++ b/src/changelog/.2.x.x/3771_graalvm-params.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="changed">
+  <issue id="3771" 
link="https://github.com/apache/logging-log4j2/issues/3771"/>
+  <description format="asciidoc">
+    Make `-Alog4j.graalvm.groupId` and `-Alog4j.graalvm.artifactId` arguments 
optional.
+  </description>
+</entry>

Reply via email to