emptyList());
+ super(Collections.emptyList());
this.elements = elements;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/internal/Annotations.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/internal/Annotations.java
new file mode 100644
index 00000000000..e809ba3d4df
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/internal/Annotations.java
@@ -0,0 +1,147 @@
+/*
+ * 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 org.apache.logging.log4j.core.config.plugins.processor.internal;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.util.Elements;
+
+public final class Annotations {
+
+ /**
+ * These are fields, methods or parameters that correspond to Log4j configuration attributes, elements, and other
+ * injected elements.
+ *
+ * Note: The annotations listed here must also be declared in
+ * {@link org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor}.
+ *
+ */
+ private static final Collection PARAMETER_ANNOTATION_NAMES = Arrays.asList(
+ "org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute",
+ "org.apache.logging.log4j.core.config.plugins.PluginConfiguration",
+ "org.apache.logging.log4j.core.config.plugins.PluginElement",
+ "org.apache.logging.log4j.core.config.plugins.PluginLoggerContext",
+ "org.apache.logging.log4j.core.config.plugins.PluginNode",
+ "org.apache.logging.log4j.core.config.plugins.PluginValue");
+ /**
+ * These are static methods that must be reachable through reflection.
+ *
+ * Note: The annotations listed here must also be declared in
+ * {@link org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor}.
+ *
+ */
+ private static final Collection FACTORY_ANNOTATION_NAMES = Arrays.asList(
+ "org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory",
+ "org.apache.logging.log4j.core.config.plugins.PluginFactory");
+ /**
+ * These must be public types with either:
+ *
+ * - A factory method.
+ * - A static method called {@code newInstance}.
+ * - A public no-argument constructor.
+ *
+ *
+ * Note: The annotations listed here must also be declared in
+ * {@link org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor}.
+ *
+ */
+ private static final Collection PLUGIN_ANNOTATION_NAMES =
+ Collections.singletonList("org.apache.logging.log4j.core.config.plugins.Plugin");
+
+ /**
+ * Reflection is also used to create constraint validators and plugin visitors.
+ *
+ * Note: The annotations listed here must also be declared in
+ * {@link org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor}.
+ *
+ */
+ private static final Collection CONSTRAINT_OR_VISITOR_ANNOTATION_NAMES = Arrays.asList(
+ "org.apache.logging.log4j.core.config.plugins.validation.Constraint",
+ "org.apache.logging.log4j.core.config.plugins.PluginVisitorStrategy");
+
+ public enum Type {
+ /**
+ * Annotation used to mark a configuration attribute, element or other injected parameters.
+ */
+ PARAMETER,
+ /**
+ * Annotation used to mark a Log4j Plugin factory method.
+ */
+ FACTORY,
+ /**
+ * Annotation used to mark a Log4j Plugin class.
+ */
+ PLUGIN,
+ /**
+ * Annotation containing the name of a
+ * {@link org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator}
+ * or
+ * {@link org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor}.
+ */
+ CONSTRAINT_OR_VISITOR,
+ /**
+ * Unknown
+ */
+ UNKNOWN
+ }
+
+ private final Map typeElementToTypeMap = new HashMap<>();
+
+ public Annotations(final Elements elements) {
+ PARAMETER_ANNOTATION_NAMES.forEach(className -> addTypeElementIfExists(elements, className, Type.PARAMETER));
+ FACTORY_ANNOTATION_NAMES.forEach(className -> addTypeElementIfExists(elements, className, Type.FACTORY));
+ PLUGIN_ANNOTATION_NAMES.forEach(className -> addTypeElementIfExists(elements, className, Type.PLUGIN));
+ CONSTRAINT_OR_VISITOR_ANNOTATION_NAMES.forEach(
+ className -> addTypeElementIfExists(elements, className, Type.CONSTRAINT_OR_VISITOR));
+ }
+
+ private void addTypeElementIfExists(Elements elements, CharSequence className, Type type) {
+ final TypeElement element = elements.getTypeElement(className);
+ if (element != null) {
+ typeElementToTypeMap.put(element, type);
+ }
+ }
+
+ public Annotations.Type classifyAnnotation(TypeElement element) {
+ return typeElementToTypeMap.getOrDefault(element, Type.UNKNOWN);
+ }
+
+ public Element getAnnotationClassValue(Element element, TypeElement annotation) {
+ // This prevents getting an "Attempt to access Class object for TypeMirror" exception
+ AnnotationMirror annotationMirror = element.getAnnotationMirrors().stream()
+ .filter(am -> am.getAnnotationType().asElement().equals(annotation))
+ .findFirst()
+ .orElseThrow(
+ () -> new IllegalStateException("No `@" + annotation + "` annotation found on " + element));
+ AnnotationValue annotationValue = annotationMirror.getElementValues().entrySet().stream()
+ .filter(e -> "value".equals(e.getKey().getSimpleName().toString()))
+ .map(Map.Entry::getValue)
+ .findFirst()
+ .orElseThrow(() ->
+ new IllegalStateException("No `value` found `@" + annotation + "` annotation on " + element));
+ DeclaredType value = (DeclaredType) annotationValue.getValue();
+ return value.asElement();
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/internal/ReachabilityMetadata.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/internal/ReachabilityMetadata.java
new file mode 100644
index 00000000000..43596361f5d
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/internal/ReachabilityMetadata.java
@@ -0,0 +1,290 @@
+/*
+ * 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 org.apache.logging.log4j.core.config.plugins.processor.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.stream.IntStream;
+import org.apache.logging.log4j.core.util.JsonUtils;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Provides support for the
+ * {@code reachability-metadata.json}
+ * file format.
+ */
+@NullMarked
+public final class ReachabilityMetadata {
+
+ /**
+ * Key used to specify the name of a field or method
+ */
+ public static final String FIELD_OR_METHOD_NAME = "name";
+ /**
+ * Key used to list the method parameter types.
+ */
+ public static final String PARAMETER_TYPES = "parameterTypes";
+ /**
+ * Key used to specify the name of a type.
+ *
+ * Since GraalVM for JDK 23 it will be called "type".
+ *
+ */
+ public static final String TYPE_NAME = "name";
+ /**
+ * Key used to specify the list of fields available for reflection.
+ */
+ public static final String FIELDS = "fields";
+ /**
+ * Key used to specify the list of methods available for reflection.
+ */
+ public static final String METHODS = "methods";
+
+ private static final class MinimalJsonWriter {
+
+ private final Appendable output;
+
+ public MinimalJsonWriter(Appendable output) {
+ this.output = output;
+ }
+
+ public void writeString(CharSequence input) throws IOException {
+ output.append('"');
+ StringBuilder sb = new StringBuilder();
+ JsonUtils.quoteAsString(input, sb);
+ output.append(sb);
+ output.append('"');
+ }
+
+ public void writeObjectStart() throws IOException {
+ output.append('{');
+ }
+
+ public void writeObjectEnd() throws IOException {
+ output.append('}');
+ }
+
+ public void writeObjectKey(CharSequence key) throws IOException {
+ writeString(key);
+ output.append(':');
+ }
+
+ public void writeArrayStart() throws IOException {
+ output.append('[');
+ }
+
+ public void writeSeparator() throws IOException {
+ output.append(',');
+ }
+
+ public void writeArrayEnd() throws IOException {
+ output.append(']');
+ }
+ }
+
+ /**
+ * Specifies a field that needs to be accessed through reflection.
+ */
+ public static final class Field implements Comparable {
+
+ private final String name;
+
+ public Field(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ void toJson(MinimalJsonWriter jsonWriter) throws IOException {
+ jsonWriter.writeObjectStart();
+ jsonWriter.writeObjectKey(FIELD_OR_METHOD_NAME);
+ jsonWriter.writeString(name);
+ jsonWriter.writeObjectEnd();
+ }
+
+ @Override
+ public int compareTo(Field other) {
+ return name.compareTo(other.name);
+ }
+ }
+
+ /**
+ * Specifies a method that needs to be accessed through reflection.
+ */
+ public static final class Method implements Comparable {
+
+ private final String name;
+ private final List parameterTypes = new ArrayList<>();
+
+ public Method(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void addParameterType(final String parameterType) {
+ parameterTypes.add(parameterType);
+ }
+
+ void toJson(MinimalJsonWriter jsonWriter) throws IOException {
+ jsonWriter.writeObjectStart();
+ jsonWriter.writeObjectKey(FIELD_OR_METHOD_NAME);
+ jsonWriter.writeString(name);
+ jsonWriter.writeSeparator();
+ jsonWriter.writeObjectKey(PARAMETER_TYPES);
+ jsonWriter.writeArrayStart();
+ boolean first = true;
+ for (String parameterType : parameterTypes) {
+ if (!first) {
+ jsonWriter.writeSeparator();
+ }
+ first = false;
+ jsonWriter.writeString(parameterType);
+ }
+ jsonWriter.writeArrayEnd();
+ jsonWriter.writeObjectEnd();
+ }
+
+ @Override
+ public int compareTo(Method other) {
+ int result = name.compareTo(other.name);
+ if (result == 0) {
+ result = parameterTypes.size() - other.parameterTypes.size();
+ }
+ if (result == 0) {
+ result = IntStream.range(0, parameterTypes.size())
+ .map(idx -> parameterTypes.get(idx).compareTo(other.parameterTypes.get(idx)))
+ .filter(r -> r != 0)
+ .findFirst()
+ .orElse(0);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Specifies a Java type that needs to be accessed through reflection.
+ */
+ public static final class Type {
+
+ private final String type;
+ private final Collection methods = new TreeSet<>();
+ private final Collection fields = new TreeSet<>();
+
+ public Type(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void addMethod(Method method) {
+ methods.add(method);
+ }
+
+ public void addField(Field field) {
+ fields.add(field);
+ }
+
+ void toJson(MinimalJsonWriter jsonWriter) throws IOException {
+ jsonWriter.writeObjectStart();
+ jsonWriter.writeObjectKey(TYPE_NAME);
+ jsonWriter.writeString(type);
+ jsonWriter.writeSeparator();
+
+ boolean first = true;
+ jsonWriter.writeObjectKey(METHODS);
+ jsonWriter.writeArrayStart();
+ for (Method method : methods) {
+ if (!first) {
+ jsonWriter.writeSeparator();
+ }
+ first = false;
+ method.toJson(jsonWriter);
+ }
+ jsonWriter.writeArrayEnd();
+ jsonWriter.writeSeparator();
+
+ first = true;
+ jsonWriter.writeObjectKey(FIELDS);
+ jsonWriter.writeArrayStart();
+ for (Field field : fields) {
+ if (!first) {
+ jsonWriter.writeSeparator();
+ }
+ first = false;
+ field.toJson(jsonWriter);
+ }
+ jsonWriter.writeArrayEnd();
+ jsonWriter.writeObjectEnd();
+ }
+ }
+
+ /**
+ * Collection of reflection metadata.
+ */
+ public static final class Reflection {
+
+ private final Collection types = new TreeSet<>(Comparator.comparing(Type::getType));
+
+ public Reflection(Collection types) {
+ this.types.addAll(types);
+ }
+
+ void toJson(MinimalJsonWriter jsonWriter) throws IOException {
+ boolean first = true;
+ jsonWriter.writeArrayStart();
+ for (Type type : types) {
+ if (!first) {
+ jsonWriter.writeSeparator();
+ }
+ first = false;
+ type.toJson(jsonWriter);
+ }
+ jsonWriter.writeArrayEnd();
+ }
+ }
+
+ /**
+ * Writes the contents of a {@code reflect-config.json} file.
+ *
+ * @param types The reflection metadata for types.
+ * @param output The object to use as output.
+ */
+ public static void writeReflectConfig(Collection types, OutputStream output) throws IOException {
+ try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
+ Reflection reflection = new Reflection(types);
+ MinimalJsonWriter jsonWriter = new MinimalJsonWriter(writer);
+ reflection.toJson(jsonWriter);
+ }
+ }
+
+ private ReachabilityMetadata() {}
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java
index 1322b47dcdf..bc28acee278 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java
@@ -20,7 +20,7 @@
* executable {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager} class in your build process.
*/
@Export
-@Version("2.20.1")
+@Version("2.25.0")
package org.apache.logging.log4j.core.config.plugins.processor;
import org.osgi.annotation.bundle.Export;
diff --git a/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/resource-config.json b/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/resource-config.json
new file mode 100644
index 00000000000..d5b51667ed9
--- /dev/null
+++ b/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/resource-config.json
@@ -0,0 +1,12 @@
+{
+ "resources": {
+ "includes": [
+ {
+ "pattern": "log4j2.*"
+ },
+ {
+ "pattern": "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml
index a7e0230406f..daaf5109cec 100644
--- a/log4j-parent/pom.xml
+++ b/log4j-parent/pom.xml
@@ -108,7 +108,6 @@
0.6.0
3.5.12
1.37
- 2.40.1
4.13.2
5.10.3
1.9.1
@@ -576,12 +575,6 @@
${jna.version}
-
- net.javacrumbs.json-unit
- json-unit
- ${json-unit.version}
-
-
junit
junit
@@ -1189,6 +1182,8 @@
org.apache.logging.log4j.docgen.processor.DescriptorGenerator
org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor
+
+ org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor
@@ -1198,6 +1193,9 @@
-Alog4j.docgen.version=${project.version}
-Alog4j.docgen.description=${project.description}
-Alog4j.docgen.typeFilter.excludePattern=${log4j.docgen.typeFilter.excludePattern}
+
+ -Alog4j.graalvm.groupId=${project.groupId}
+ -Alog4j.graalvm.artifactId=${project.artifactId}
only