Skip to content

Commit

Permalink
Add log4j.plugin.enableBndAnnotations option to PluginProcessor
Browse files Browse the repository at this point in the history
This adds a `log4j.plugin.enableBndAnnotations` option to the `PluginProcessor`. Its default value is inferred from the compiler classpath.

We also rename the `pluginPackage` option to a more coherent `log4j.plugin.package` option.

Closes #3251
  • Loading branch information
ppkarwasz committed Nov 30, 2024
1 parent 7fd3a13 commit e6e6ddd
Show file tree
Hide file tree
Showing 7 changed files with 384 additions and 137 deletions.
2 changes: 1 addition & 1 deletion log4j-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,7 @@
<id>default-testCompile</id>
<configuration>
<compilerArgs combine.children="append">
<arg>-ApluginPackage=${log4jPluginPackageForTests}</arg>
<arg>-Alog4j.plugin.package=${log4jPluginPackageForTests}</arg>
</compilerArgs>
</configuration>
</execution>
Expand Down
49 changes: 45 additions & 4 deletions log4j-plugin-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@
<name>Apache Log4j Plugin Processor</name>
<description>Log4j Plugin Annotation Processor</description>

<properties>
<log4jParentDir>${basedir}/..</log4jParentDir>
</properties>

<dependencies>

<dependency>
Expand All @@ -47,6 +43,51 @@
<artifactId>log4j-plugins</artifactId>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<plugins>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>test-no-bnd-annotations</id>
<configuration>
<classpathDependencyExcludes>
<exclude>biz.aQute.bnd:biz.aQute.bnd.annotation</exclude>
</classpathDependencyExcludes>
</configuration>
</execution>
</executions>
</plugin>

</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -57,41 +57,71 @@
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.
* <p>
* 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:
* </p>
* <pre>
* {@code <log4j.plugin.package>.plugins.Log4jPlugins}
* </pre>
* <p>
* where {@code <log4j.plugin.package>} is the effective value of the {@link #PLUGIN_PACKAGE} option.
* </p>
*/
@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.
* <p>
* The default behavior depends on the presence of {@code biz.aQute.bnd.annotation} on the classpath.
* </p>
*/
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}
* <p>
* If absent, the value of this option is the common prefix of all Log4j Plugin classes.
* </p>
*/
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<String> getSupportedOptions() {
return Set.of(ENABLE_BND_ANNOTATIONS, PLUGIN_PACKAGE);
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}

@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
final Map<String, String> 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<PluginEntry> list = new ArrayList<>();
Expand All @@ -115,14 +145,12 @@ private void error(final CharSequence message) {

private String collectPlugins(
String packageName, final Iterable<? extends Element> elements, final List<PluginEntry> 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) {
Expand All @@ -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()) {
Expand All @@ -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));
}
}
Expand All @@ -167,12 +196,16 @@ private void writeClassFile(final String pkg, final List<PluginEntry> 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[] {");
Expand Down Expand Up @@ -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<String, String> 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.
*/
Expand Down
Loading

0 comments on commit e6e6ddd

Please sign in to comment.