diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml
index c2b3a8e651e..0d45bf9a96f 100644
--- a/log4j-parent/pom.xml
+++ b/log4j-parent/pom.xml
@@ -865,7 +865,7 @@
default-testCompile
- -ApluginPackage=${log4jPluginPackageForTests}
+ -Alog4j.plugin.package=${log4jPluginPackageForTests}
diff --git a/log4j-plugin-processor/pom.xml b/log4j-plugin-processor/pom.xml
index 26034033aa6..7722a065305 100644
--- a/log4j-plugin-processor/pom.xml
+++ b/log4j-plugin-processor/pom.xml
@@ -31,10 +31,6 @@
Apache Log4j Plugin Processor
Log4j Plugin Annotation Processor
-
- ${basedir}/..
-
-
@@ -47,6 +43,51 @@
log4j-plugins
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ commons-io
+ commons-io
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+ org.junit-pioneer
+ junit-pioneer
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ test-no-bnd-annotations
+
+
+ biz.aQute.bnd:biz.aQute.bnd.annotation
+
+
+
+
+
+
+
+
+
diff --git a/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
index 947c0278c9b..40f4f93c62f 100644
--- a/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
+++ b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
@@ -57,25 +57,56 @@
import org.apache.logging.log4j.plugins.PluginAliases;
import org.apache.logging.log4j.plugins.model.PluginEntry;
import org.apache.logging.log4j.util.Strings;
+import org.jspecify.annotations.NullMarked;
/**
- * Annotation processor for pre-scanning Log4j plugins. This generates implementation classes extending
- * {@link org.apache.logging.log4j.plugins.model.PluginService} with a list of {@link PluginEntry} instances
- * discovered from plugin annotations. By default, this will use the most specific package name it can derive
- * from where the annotated plugins are located in a subpackage {@code plugins}. The output base package name
- * can be overridden via the {@code pluginPackage} annotation processor option.
+ * Annotation processor to generate a {@link org.apache.logging.log4j.plugins.model.PluginService} implementation.
+ *
+ * This generates a {@link org.apache.logging.log4j.plugins.model.PluginService} implementation with a list of
+ * {@link PluginEntry} instances.
+ * The fully qualified class name of the generated service is:
+ *
+ *
+ * {@code .plugins.Log4jPlugins}
+ *
+ *
+ * where {@code } is the effective value of the {@link #PLUGIN_PACKAGE} option.
+ *
*/
+@NullMarked
@SupportedAnnotationTypes({"org.apache.logging.log4j.plugins.*", "org.apache.logging.log4j.core.config.plugins.*"})
@ServiceProvider(value = Processor.class, resolution = Resolution.OPTIONAL)
public class PluginProcessor extends AbstractProcessor {
- // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing
+ /**
+ * Option name to enable or disable the generation of {@link aQute.bnd.annotation.spi.ServiceConsumer} annotations.
+ *
+ * The default behavior depends on the presence of {@code biz.aQute.bnd.annotation} on the classpath.
+ *
+ */
+ public static final String ENABLE_BND_ANNOTATIONS = "log4j.plugin.enableBndAnnotations";
+
+ /**
+ * Option name to determine the package containing the generated {@link org.apache.logging.log4j.plugins.model.PluginService}
+ *
+ * If absent, the value of this option is the common prefix of all Log4j Plugin classes.
+ *
+ */
+ public static final String PLUGIN_PACKAGE = "log4j.plugin.package";
private static final String SERVICE_FILE_NAME =
"META-INF/services/org.apache.logging.log4j.plugins.model.PluginService";
+ private boolean enableBndAnnotations;
+ private String packageName = "";
+
public PluginProcessor() {}
+ @Override
+ public Set getSupportedOptions() {
+ return Set.of(ENABLE_BND_ANNOTATIONS, PLUGIN_PACKAGE);
+ }
+
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
@@ -83,15 +114,14 @@ public SourceVersion getSupportedSourceVersion() {
@Override
public boolean process(final Set extends TypeElement> annotations, final RoundEnvironment roundEnv) {
- final Map options = processingEnv.getOptions();
- String packageName = options.get("pluginPackage");
+ handleOptions(processingEnv.getOptions());
final Messager messager = processingEnv.getMessager();
messager.printMessage(Kind.NOTE, "Processing Log4j annotations");
try {
final Set extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);
if (elements.isEmpty()) {
messager.printMessage(Kind.NOTE, "No elements to process");
- return false;
+ return true;
}
messager.printMessage(Kind.NOTE, "Retrieved " + elements.size() + " Plugin elements");
final List list = new ArrayList<>();
@@ -115,14 +145,12 @@ private void error(final CharSequence message) {
private String collectPlugins(
String packageName, final Iterable extends Element> elements, final List list) {
- final boolean calculatePackage = packageName == null;
+ final boolean calculatePackage = packageName.isEmpty();
final var pluginVisitor = new PluginElementVisitor();
final var pluginAliasesVisitor = new PluginAliasesElementVisitor();
for (final Element element : elements) {
- final Plugin plugin = element.getAnnotation(Plugin.class);
- if (plugin == null) {
- continue;
- }
+ // The elements must be annotated with `Plugin`
+ Plugin plugin = element.getAnnotation(Plugin.class);
final var entry = element.accept(pluginVisitor, plugin);
list.add(entry);
if (calculatePackage) {
@@ -135,11 +163,11 @@ private String collectPlugins(
private String calculatePackage(Element element, String packageName) {
final Name name = processingEnv.getElementUtils().getPackageOf(element).getQualifiedName();
- if (name == null) {
- return null;
+ if (name.isEmpty()) {
+ return "";
}
final String pkgName = name.toString();
- if (packageName == null) {
+ if (packageName.isEmpty()) {
return pkgName;
}
if (pkgName.length() == packageName.length()) {
@@ -158,6 +186,7 @@ private void writeServiceFile(final String pkgName) throws IOException {
.createResource(StandardLocation.CLASS_OUTPUT, Strings.EMPTY, SERVICE_FILE_NAME);
try (final PrintWriter writer =
new PrintWriter(new BufferedWriter(new OutputStreamWriter(fileObject.openOutputStream(), UTF_8)))) {
+ writer.println("# Generated by " + PluginProcessor.class.getName());
writer.println(createFqcn(pkgName));
}
}
@@ -167,12 +196,16 @@ private void writeClassFile(final String pkg, final List list) {
try (final PrintWriter writer = createSourceFile(fqcn)) {
writer.println("package " + pkg + ".plugins;");
writer.println("");
- writer.println("import aQute.bnd.annotation.Resolution;");
- writer.println("import aQute.bnd.annotation.spi.ServiceProvider;");
+ if (enableBndAnnotations) {
+ writer.println("import aQute.bnd.annotation.Resolution;");
+ writer.println("import aQute.bnd.annotation.spi.ServiceProvider;");
+ }
writer.println("import org.apache.logging.log4j.plugins.model.PluginEntry;");
writer.println("import org.apache.logging.log4j.plugins.model.PluginService;");
writer.println("");
- writer.println("@ServiceProvider(value = PluginService.class, resolution = Resolution.OPTIONAL)");
+ if (enableBndAnnotations) {
+ writer.println("@ServiceProvider(value = PluginService.class, resolution = Resolution.OPTIONAL)");
+ }
writer.println("public class Log4jPlugins extends PluginService {");
writer.println("");
writer.println(" private static final PluginEntry[] ENTRIES = new PluginEntry[] {");
@@ -282,6 +315,25 @@ private String commonPrefix(final String str1, final String str2) {
return str1.substring(0, minLength);
}
+ private static boolean isServiceConsumerClassPresent() {
+ try {
+ Class.forName("aQute.bnd.annotation.spi.ServiceConsumer");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void handleOptions(Map options) {
+ packageName = options.getOrDefault(PLUGIN_PACKAGE, "");
+ String enableBndAnnotationsOption = options.get(ENABLE_BND_ANNOTATIONS);
+ if (enableBndAnnotationsOption != null) {
+ this.enableBndAnnotations = !"false".equals(enableBndAnnotationsOption);
+ } else {
+ this.enableBndAnnotations = isServiceConsumerClassPresent();
+ }
+ }
+
/**
* ElementVisitor to scan the PluginAliases annotation.
*/
diff --git a/log4j-plugin-processor/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java b/log4j-plugin-processor/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
new file mode 100644
index 00000000000..b6c2e7033ed
--- /dev/null
+++ b/log4j-plugin-processor/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.plugin.processor;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.stream.Stream;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import org.apache.commons.io.FileUtils;
+import org.apache.logging.log4j.plugins.model.PluginEntry;
+import org.apache.logging.log4j.plugins.model.PluginNamespace;
+import org.apache.logging.log4j.plugins.model.PluginService;
+import org.apache.logging.log4j.plugins.model.PluginType;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junitpioneer.jupiter.Issue;
+
+class PluginProcessorTest {
+
+ private static final String CORE_NAMESPACE = "Core";
+ private static final String TEST_NAMESPACE = "Test";
+
+ private static PathClassLoader classLoader;
+ private static PluginService pluginService;
+
+ @BeforeAll
+ static void setup() throws Exception {
+ classLoader = new PathClassLoader();
+ pluginService = generatePluginService("example");
+ }
+
+ @AfterAll
+ static void cleanup() {
+ pluginService = null;
+ classLoader = null;
+ }
+
+ private static PluginService generatePluginService(String expectedPluginPackage, String... options)
+ throws Exception {
+ // Source file
+ URL fakePluginUrl = PluginProcessorTest.class.getResource("/example/FakePlugin.java");
+ assertThat(fakePluginUrl).isNotNull();
+ Path fakePluginPath = Paths.get(fakePluginUrl.toURI());
+ // Collect warnings
+ WarningCollector collector = new WarningCollector();
+ String fqcn = expectedPluginPackage + ".plugins.Log4jPlugins";
+ Path outputDir = Files.createTempDirectory("PluginProcessorTest");
+
+ try {
+ // Instantiate the tooling
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ StandardJavaFileManager fileManager = compiler.getStandardFileManager(collector, Locale.ROOT, UTF_8);
+
+ // Populate sources
+ Iterable extends JavaFileObject> sources = fileManager.getJavaFileObjects(fakePluginPath);
+
+ // Set the target path used by `DescriptorGenerator` to dump the generated files
+ fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Set.of(outputDir));
+ fileManager.setLocationFromPaths(StandardLocation.SOURCE_OUTPUT, Set.of(outputDir));
+
+ // Compile the sources
+ final JavaCompiler.CompilationTask task =
+ compiler.getTask(null, fileManager, collector, Arrays.asList(options), null, sources);
+ task.setProcessors(List.of(new PluginProcessor()));
+ task.call();
+
+ // Verify successful compilation
+ List> diagnostics = collector.getDiagnostics();
+ assertThat(diagnostics).isEmpty();
+
+ // Find the PluginService class
+ Path pluginServicePath = outputDir.resolve(fqcn.replaceAll("\\.", "/") + ".class");
+ assertThat(pluginServicePath).exists();
+ Class> pluginServiceClass = classLoader.defineClass(fqcn, pluginServicePath);
+ return (PluginService) pluginServiceClass.getConstructor().newInstance();
+ } finally {
+ FileUtils.deleteDirectory(outputDir.toFile());
+ }
+ }
+
+ @Test
+ void namespaceFound() {
+ assertThat(pluginService.size()).as("Number of namespaces").isNotZero();
+ assertThat(pluginService.getNamespace(CORE_NAMESPACE))
+ .as("Namespace %s", CORE_NAMESPACE)
+ .isNotNull();
+ }
+
+ static Stream checkFakePluginInformation() {
+ return Stream.of("Fake", "AnotherFake", "StillFake");
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void checkFakePluginInformation(String aliasName) {
+ PluginNamespace namespace = pluginService.getNamespace(CORE_NAMESPACE);
+ assertThat(namespace).isNotNull();
+ PluginType> pluginType = namespace.get(aliasName);
+ assertThat(pluginType).as("Plugin type with alias `%s`", aliasName).isNotNull();
+ verifyPluginEntry(
+ pluginType.getPluginEntry(),
+ aliasName.toLowerCase(Locale.ROOT),
+ CORE_NAMESPACE,
+ "Fake",
+ "example.FakePlugin",
+ "Fake",
+ true,
+ true);
+ }
+
+ @Test
+ void checkNestedPluginInformation() {
+ PluginNamespace namespace = pluginService.getNamespace(TEST_NAMESPACE);
+ assertThat(namespace).isNotNull();
+ PluginType> pluginType = namespace.get("Nested");
+ assertThat(pluginType).as("Plugin type with alias `%s`", "Nested").isNotNull();
+ verifyPluginEntry(
+ pluginType.getPluginEntry(),
+ "nested",
+ TEST_NAMESPACE,
+ "Nested",
+ "example.FakePlugin$Nested",
+ "",
+ false,
+ false);
+ }
+
+ @Test
+ void checkPluginPackageOption() throws Exception {
+ PluginService pluginService = generatePluginService("com.example", "-Alog4j.plugin.package=com.example");
+ assertThat(pluginService).isNotNull();
+ }
+
+ @Test
+ void checkEnableBndAnnotationsOption() {
+ // If we don't have the annotations on the classpath compilation should fail
+ assumeThat(areBndAnnotationsAbsent()).isTrue();
+ Assertions.assertThrows(
+ NullPointerException.class,
+ () -> generatePluginService(
+ "com.example.bnd",
+ "-Alog4j.plugin.package=com.example.bnd",
+ "-Alog4j.plugin.enableBndAnnotations=true"));
+ }
+
+ private boolean areBndAnnotationsAbsent() {
+ try {
+ Class.forName("aQute.bnd.annotation.spi.ServiceConsumer");
+ return false;
+ } catch (ClassNotFoundException e) {
+ return true;
+ }
+ }
+
+ private void verifyPluginEntry(
+ PluginEntry actual,
+ String key,
+ String namespace,
+ String name,
+ String className,
+ String elementType,
+ boolean deferChildren,
+ boolean printable) {
+ assertThat(actual.key()).as("Key").isEqualTo(key);
+ assertThat(actual.namespace()).as("Namespace").isEqualTo(namespace);
+ assertThat(actual.name()).as("Name").isEqualTo(name);
+ assertThat(actual.className()).as("Class name").isEqualTo(className);
+ assertThat(actual.elementType()).as("Element type").isEqualTo(elementType);
+ assertThat(actual.deferChildren()).as("Deferred children").isEqualTo(deferChildren);
+ assertThat(actual.printable()).as("Printable").isEqualTo(printable);
+ }
+
+ @Test
+ @Issue("https://github.com/apache/logging-log4j2/issues/1520")
+ public void testReproducibleOutputOrder() {
+ assertThat(pluginService.getEntries()).isSorted();
+ }
+
+ private static class WarningCollector implements DiagnosticListener {
+
+ private final List> diagnostics = new ArrayList<>();
+
+ private WarningCollector() {}
+
+ public List> getDiagnostics() {
+ return diagnostics;
+ }
+
+ @Override
+ public void report(Diagnostic extends JavaFileObject> diagnostic) {
+ switch (diagnostic.getKind()) {
+ case ERROR:
+ case WARNING:
+ case MANDATORY_WARNING:
+ diagnostics.add(diagnostic);
+ break;
+ default:
+ }
+ }
+ }
+
+ private static class PathClassLoader extends ClassLoader {
+
+ public PathClassLoader() {
+ super(PluginProcessorTest.class.getClassLoader());
+ }
+
+ public Class> defineClass(String name, Path path) throws IOException {
+ final byte[] bytes;
+ try (InputStream inputStream = Files.newInputStream(path)) {
+ bytes = inputStream.readAllBytes();
+ }
+ return defineClass(name, bytes, 0, bytes.length);
+ }
+ }
+}
diff --git a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/FakePlugin.java b/log4j-plugin-processor/src/test/resources/example/FakePlugin.java
similarity index 92%
rename from log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/FakePlugin.java
rename to log4j-plugin-processor/src/test/resources/example/FakePlugin.java
index 87166b25f3f..decc8acf27e 100644
--- a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/FakePlugin.java
+++ b/log4j-plugin-processor/src/test/resources/example/FakePlugin.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.plugins.test.validation;
+package example;
import org.apache.logging.log4j.plugins.Configurable;
import org.apache.logging.log4j.plugins.Namespace;
@@ -24,7 +24,7 @@
/**
* Test plugin class for unit tests.
*/
-@Configurable(deferChildren = true)
+@Configurable(deferChildren = true, printObject = true)
@Plugin("Fake")
@PluginAliases({"AnotherFake", "StillFake"})
public class FakePlugin {
diff --git a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
deleted file mode 100644
index c1ecc9aa403..00000000000
--- a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.plugin.processor;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-
-import org.apache.logging.log4j.plugins.Configurable;
-import org.apache.logging.log4j.plugins.Plugin;
-import org.apache.logging.log4j.plugins.PluginAliases;
-import org.apache.logging.log4j.plugins.di.Keys;
-import org.apache.logging.log4j.plugins.model.PluginService;
-import org.apache.logging.log4j.plugins.model.PluginType;
-import org.apache.logging.log4j.plugins.test.validation.FakePlugin;
-import org.apache.logging.log4j.plugins.test.validation.plugins.Log4jPlugins;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junitpioneer.jupiter.Issue;
-
-@RunWith(JUnit4.class)
-public class PluginProcessorTest {
-
- private static PluginService pluginService;
-
- private final Plugin p = FakePlugin.class.getAnnotation(Plugin.class);
- private final Configurable c = FakePlugin.class.getAnnotation(Configurable.class);
- private final String ns = Keys.getNamespace(FakePlugin.class);
-
- @BeforeClass
- public static void setUpClass() {
- pluginService = new Log4jPlugins();
- }
-
- @Test
- public void testTestCategoryFound() throws Exception {
- assertNotNull("No plugin annotation on FakePlugin.", p);
- final var namespace = pluginService.getNamespace(ns);
- assertNotEquals("No plugins were found.", 0, pluginService.size());
- assertNotNull("The namespace '" + ns + "' was not found.", namespace);
- assertFalse(namespace.isEmpty());
- }
-
- @Test
- public void testFakePluginFoundWithCorrectInformation() throws Exception {
- final var testCategory = pluginService.getNamespace(ns);
- assertNotNull(testCategory);
- final PluginType> type = testCategory.get(p.value());
- assertNotNull(type);
- verifyFakePluginEntry(p.value(), type);
- }
-
- @Test
- public void testFakePluginAliasesContainSameInformation() throws Exception {
- final PluginAliases aliases = FakePlugin.class.getAnnotation(PluginAliases.class);
- for (final String alias : aliases.value()) {
- final var testCategory = pluginService.getNamespace(ns);
- assertNotNull(testCategory);
- final PluginType> type = testCategory.get(alias);
- assertNotNull(type);
- verifyFakePluginEntry(alias, type);
- }
- }
-
- private void verifyFakePluginEntry(final String name, final PluginType> fake) {
- assertNotNull("The plugin '" + name.toLowerCase() + "' was not found.", fake);
- assertEquals(FakePlugin.class.getName(), fake.getPluginEntry().className());
- assertEquals(name.toLowerCase(), fake.getKey());
- assertEquals(Plugin.EMPTY, c.elementType());
- assertEquals(p.value(), fake.getName());
- assertEquals(c.printObject(), fake.isObjectPrintable());
- assertEquals(c.deferChildren(), fake.isDeferChildren());
- }
-
- @Test
- public void testNestedPlugin() throws Exception {
- final Plugin p = FakePlugin.Nested.class.getAnnotation(Plugin.class);
- final var testCategory = pluginService.getNamespace(Keys.getNamespace(FakePlugin.Nested.class));
- assertNotNull(testCategory);
- final PluginType> nested = testCategory.get(p.value());
- assertNotNull(nested);
- assertEquals(p.value().toLowerCase(), nested.getKey());
- assertEquals(FakePlugin.Nested.class.getName(), nested.getPluginEntry().className());
- assertEquals(p.value(), nested.getName());
- }
-
- @Test
- @Issue("https://github.com/apache/logging-log4j2/issues/1520")
- public void testReproducibleOutputOrder() {
- assertThat(pluginService.getEntries()).isSorted();
- }
-}
diff --git a/src/changelog/.3.x.x/3151_plugin_processor_bnd_annotations.xml b/src/changelog/.3.x.x/3151_plugin_processor_bnd_annotations.xml
new file mode 100644
index 00000000000..459f25876d5
--- /dev/null
+++ b/src/changelog/.3.x.x/3151_plugin_processor_bnd_annotations.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ Add `log4j.plugin.enableBndAnnotations` option to `PluginProcessor`.
+ This also renames the `pluginPackage` option to `log4j.plugin.package`.
+
+