57
57
import org .apache .logging .log4j .plugins .PluginAliases ;
58
58
import org .apache .logging .log4j .plugins .model .PluginEntry ;
59
59
import org .apache .logging .log4j .util .Strings ;
60
+ import org .jspecify .annotations .NullMarked ;
60
61
61
62
/**
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>
67
75
*/
76
+ @ NullMarked
68
77
@ SupportedAnnotationTypes ({"org.apache.logging.log4j.plugins.*" , "org.apache.logging.log4j.core.config.plugins.*" })
69
78
@ ServiceProvider (value = Processor .class , resolution = Resolution .OPTIONAL )
70
79
public class PluginProcessor extends AbstractProcessor {
71
80
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" ;
73
96
74
97
private static final String SERVICE_FILE_NAME =
75
98
"META-INF/services/org.apache.logging.log4j.plugins.model.PluginService" ;
76
99
100
+ private boolean enableBndAnnotations ;
101
+ private String packageName = "" ;
102
+
77
103
public PluginProcessor () {}
78
104
105
+ @ Override
106
+ public Set <String > getSupportedOptions () {
107
+ return Set .of (ENABLE_BND_ANNOTATIONS , PLUGIN_PACKAGE );
108
+ }
109
+
79
110
@ Override
80
111
public SourceVersion getSupportedSourceVersion () {
81
112
return SourceVersion .latest ();
82
113
}
83
114
84
115
@ Override
85
116
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 ());
88
118
final Messager messager = processingEnv .getMessager ();
89
119
messager .printMessage (Kind .NOTE , "Processing Log4j annotations" );
90
120
try {
91
121
final Set <? extends Element > elements = roundEnv .getElementsAnnotatedWith (Plugin .class );
92
122
if (elements .isEmpty ()) {
93
123
messager .printMessage (Kind .NOTE , "No elements to process" );
94
- return false ;
124
+ return true ;
95
125
}
96
126
messager .printMessage (Kind .NOTE , "Retrieved " + elements .size () + " Plugin elements" );
97
127
final List <PluginEntry > list = new ArrayList <>();
@@ -115,14 +145,12 @@ private void error(final CharSequence message) {
115
145
116
146
private String collectPlugins (
117
147
String packageName , final Iterable <? extends Element > elements , final List <PluginEntry > list ) {
118
- final boolean calculatePackage = packageName == null ;
148
+ final boolean calculatePackage = packageName . isEmpty () ;
119
149
final var pluginVisitor = new PluginElementVisitor ();
120
150
final var pluginAliasesVisitor = new PluginAliasesElementVisitor ();
121
151
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 );
126
154
final var entry = element .accept (pluginVisitor , plugin );
127
155
list .add (entry );
128
156
if (calculatePackage ) {
@@ -135,11 +163,11 @@ private String collectPlugins(
135
163
136
164
private String calculatePackage (Element element , String packageName ) {
137
165
final Name name = processingEnv .getElementUtils ().getPackageOf (element ).getQualifiedName ();
138
- if (name == null ) {
139
- return null ;
166
+ if (name . isEmpty () ) {
167
+ return "" ;
140
168
}
141
169
final String pkgName = name .toString ();
142
- if (packageName == null ) {
170
+ if (packageName . isEmpty () ) {
143
171
return pkgName ;
144
172
}
145
173
if (pkgName .length () == packageName .length ()) {
@@ -158,6 +186,7 @@ private void writeServiceFile(final String pkgName) throws IOException {
158
186
.createResource (StandardLocation .CLASS_OUTPUT , Strings .EMPTY , SERVICE_FILE_NAME );
159
187
try (final PrintWriter writer =
160
188
new PrintWriter (new BufferedWriter (new OutputStreamWriter (fileObject .openOutputStream (), UTF_8 )))) {
189
+ writer .println ("# Generated by " + PluginProcessor .class .getName ());
161
190
writer .println (createFqcn (pkgName ));
162
191
}
163
192
}
@@ -167,12 +196,16 @@ private void writeClassFile(final String pkg, final List<PluginEntry> list) {
167
196
try (final PrintWriter writer = createSourceFile (fqcn )) {
168
197
writer .println ("package " + pkg + ".plugins;" );
169
198
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
+ }
172
203
writer .println ("import org.apache.logging.log4j.plugins.model.PluginEntry;" );
173
204
writer .println ("import org.apache.logging.log4j.plugins.model.PluginService;" );
174
205
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
+ }
176
209
writer .println ("public class Log4jPlugins extends PluginService {" );
177
210
writer .println ("" );
178
211
writer .println (" private static final PluginEntry[] ENTRIES = new PluginEntry[] {" );
@@ -282,6 +315,25 @@ private String commonPrefix(final String str1, final String str2) {
282
315
return str1 .substring (0 , minLength );
283
316
}
284
317
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
+
285
337
/**
286
338
* ElementVisitor to scan the PluginAliases annotation.
287
339
*/
0 commit comments