5757import org .apache .logging .log4j .plugins .PluginAliases ;
5858import org .apache .logging .log4j .plugins .model .PluginEntry ;
5959import org .apache .logging .log4j .util .Strings ;
60+ import org .jspecify .annotations .NullMarked ;
6061
6162/**
62- * Annotation processor for pre-scanning Log4j plugins. This generates implementation classes extending
63- * {@link org.apache.logging.log4j.plugins.model.PluginService} with a list of {@link PluginEntry} instances
64- * discovered from plugin annotations. By default, this will use the most specific package name it can derive
65- * from where the annotated plugins are located in a subpackage {@code plugins}. The output base package name
66- * can be overridden via the {@code pluginPackage} annotation processor option.
63+ * Annotation processor to generate a {@link org.apache.logging.log4j.plugins.model.PluginService} implementation.
64+ * <p>
65+ * This generates a {@link org.apache.logging.log4j.plugins.model.PluginService} implementation with a list of
66+ * {@link PluginEntry} instances.
67+ * The fully qualified class name of the generated service is:
68+ * </p>
69+ * <pre>
70+ * {@code <log4j.plugin.package>.plugins.Log4jPlugins}
71+ * </pre>
72+ * <p>
73+ * where {@code <log4j.plugin.package>} is the effective value of the {@link #PLUGIN_PACKAGE} option.
74+ * </p>
6775 */
76+ @ NullMarked
6877@ SupportedAnnotationTypes ({"org.apache.logging.log4j.plugins.*" , "org.apache.logging.log4j.core.config.plugins.*" })
6978@ ServiceProvider (value = Processor .class , resolution = Resolution .OPTIONAL )
7079public class PluginProcessor extends AbstractProcessor {
7180
72- // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing
81+ /**
82+ * Option name to enable or disable the generation of {@link aQute.bnd.annotation.spi.ServiceConsumer} annotations.
83+ * <p>
84+ * The default behavior depends on the presence of {@code biz.aQute.bnd.annotation} on the classpath.
85+ * </p>
86+ */
87+ public static final String ENABLE_BND_ANNOTATIONS = "log4j.plugin.enableBndAnnotations" ;
88+
89+ /**
90+ * Option name to determine the package containing the generated {@link org.apache.logging.log4j.plugins.model.PluginService}
91+ * <p>
92+ * If absent, the value of this option is the common prefix of all Log4j Plugin classes.
93+ * </p>
94+ */
95+ public static final String PLUGIN_PACKAGE = "log4j.plugin.package" ;
7396
7497 private static final String SERVICE_FILE_NAME =
7598 "META-INF/services/org.apache.logging.log4j.plugins.model.PluginService" ;
7699
100+ private boolean enableBndAnnotations ;
101+ private String packageName = "" ;
102+
77103 public PluginProcessor () {}
78104
105+ @ Override
106+ public Set <String > getSupportedOptions () {
107+ return Set .of (ENABLE_BND_ANNOTATIONS , PLUGIN_PACKAGE );
108+ }
109+
79110 @ Override
80111 public SourceVersion getSupportedSourceVersion () {
81112 return SourceVersion .latest ();
82113 }
83114
84115 @ Override
85116 public boolean process (final Set <? extends TypeElement > annotations , final RoundEnvironment roundEnv ) {
86- final Map <String , String > options = processingEnv .getOptions ();
87- String packageName = options .get ("pluginPackage" );
117+ handleOptions (processingEnv .getOptions ());
88118 final Messager messager = processingEnv .getMessager ();
89119 messager .printMessage (Kind .NOTE , "Processing Log4j annotations" );
90120 try {
91121 final Set <? extends Element > elements = roundEnv .getElementsAnnotatedWith (Plugin .class );
92122 if (elements .isEmpty ()) {
93123 messager .printMessage (Kind .NOTE , "No elements to process" );
94- return false ;
124+ return true ;
95125 }
96126 messager .printMessage (Kind .NOTE , "Retrieved " + elements .size () + " Plugin elements" );
97127 final List <PluginEntry > list = new ArrayList <>();
@@ -115,14 +145,12 @@ private void error(final CharSequence message) {
115145
116146 private String collectPlugins (
117147 String packageName , final Iterable <? extends Element > elements , final List <PluginEntry > list ) {
118- final boolean calculatePackage = packageName == null ;
148+ final boolean calculatePackage = packageName . isEmpty () ;
119149 final var pluginVisitor = new PluginElementVisitor ();
120150 final var pluginAliasesVisitor = new PluginAliasesElementVisitor ();
121151 for (final Element element : elements ) {
122- final Plugin plugin = element .getAnnotation (Plugin .class );
123- if (plugin == null ) {
124- continue ;
125- }
152+ // The elements must be annotated with `Plugin`
153+ Plugin plugin = element .getAnnotation (Plugin .class );
126154 final var entry = element .accept (pluginVisitor , plugin );
127155 list .add (entry );
128156 if (calculatePackage ) {
@@ -135,11 +163,11 @@ private String collectPlugins(
135163
136164 private String calculatePackage (Element element , String packageName ) {
137165 final Name name = processingEnv .getElementUtils ().getPackageOf (element ).getQualifiedName ();
138- if (name == null ) {
139- return null ;
166+ if (name . isEmpty () ) {
167+ return "" ;
140168 }
141169 final String pkgName = name .toString ();
142- if (packageName == null ) {
170+ if (packageName . isEmpty () ) {
143171 return pkgName ;
144172 }
145173 if (pkgName .length () == packageName .length ()) {
@@ -158,6 +186,7 @@ private void writeServiceFile(final String pkgName) throws IOException {
158186 .createResource (StandardLocation .CLASS_OUTPUT , Strings .EMPTY , SERVICE_FILE_NAME );
159187 try (final PrintWriter writer =
160188 new PrintWriter (new BufferedWriter (new OutputStreamWriter (fileObject .openOutputStream (), UTF_8 )))) {
189+ writer .println ("# Generated by " + PluginProcessor .class .getName ());
161190 writer .println (createFqcn (pkgName ));
162191 }
163192 }
@@ -167,12 +196,16 @@ private void writeClassFile(final String pkg, final List<PluginEntry> list) {
167196 try (final PrintWriter writer = createSourceFile (fqcn )) {
168197 writer .println ("package " + pkg + ".plugins;" );
169198 writer .println ("" );
170- writer .println ("import aQute.bnd.annotation.Resolution;" );
171- writer .println ("import aQute.bnd.annotation.spi.ServiceProvider;" );
199+ if (enableBndAnnotations ) {
200+ writer .println ("import aQute.bnd.annotation.Resolution;" );
201+ writer .println ("import aQute.bnd.annotation.spi.ServiceProvider;" );
202+ }
172203 writer .println ("import org.apache.logging.log4j.plugins.model.PluginEntry;" );
173204 writer .println ("import org.apache.logging.log4j.plugins.model.PluginService;" );
174205 writer .println ("" );
175- writer .println ("@ServiceProvider(value = PluginService.class, resolution = Resolution.OPTIONAL)" );
206+ if (enableBndAnnotations ) {
207+ writer .println ("@ServiceProvider(value = PluginService.class, resolution = Resolution.OPTIONAL)" );
208+ }
176209 writer .println ("public class Log4jPlugins extends PluginService {" );
177210 writer .println ("" );
178211 writer .println (" private static final PluginEntry[] ENTRIES = new PluginEntry[] {" );
@@ -282,6 +315,25 @@ private String commonPrefix(final String str1, final String str2) {
282315 return str1 .substring (0 , minLength );
283316 }
284317
318+ private static boolean isServiceConsumerClassPresent () {
319+ try {
320+ Class .forName ("aQute.bnd.annotation.spi.ServiceConsumer" );
321+ return true ;
322+ } catch (ClassNotFoundException e ) {
323+ return false ;
324+ }
325+ }
326+
327+ private void handleOptions (Map <String , String > options ) {
328+ packageName = options .getOrDefault (PLUGIN_PACKAGE , "" );
329+ String enableBndAnnotationsOption = options .get (ENABLE_BND_ANNOTATIONS );
330+ if (enableBndAnnotationsOption != null ) {
331+ this .enableBndAnnotations = !"false" .equals (enableBndAnnotationsOption );
332+ } else {
333+ this .enableBndAnnotations = isServiceConsumerClassPresent ();
334+ }
335+ }
336+
285337 /**
286338 * ElementVisitor to scan the PluginAliases annotation.
287339 */
0 commit comments