Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add log4j.plugin.enableBndAnnotations option to PluginProcessor #3258

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Comment on lines +319 to +321
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks for ServiceConsumer on the plugin processor's classpath, while it should look for it on the application classpath.

} 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
Loading