When working with custom bundle annotations one may find that @Attribute
and @Directive
have limitations in scenarios where multiple bundle annotations are added to a single custom bundle annotation because there are no discriminators expressing which bundle annotation to associate the attribute or directive; and it’s likely that associating these with all bundle annotations produces unexpected or even incorrect results. Bnd provides a solution which takes advantage of its macro support during annotation processing.
When an annotation is meta-annotated with a bundle annotation the values and defaults of the host are loaded as properties, prefixed with #
, into the macro processing scope. The effect is that these values are accessible using a macro pattern (like ${}
).
@Capability(
name = "${#name}", // <-- accesses the hosts 'name()' value
namespace = "osgi.cdi.extension",
version = "${#version}" // <-- accesses the hosts 'version()' value
)
@Requirement(
name = "osgi.cdi",
namespace = "osgi.implementation",
version = "1.0.0")
@interface CDIExtension {
String name();
String version();
}
Annotation methods may return several different types and they are converted to strings according to the following table:
Return type | Conversion |
---|---|
boolean , byte , char , double , float , int , long , short |
String.valueOf() |
java.lang.Class |
Class.getName() |
java.lang.Enum |
Enum.name() |
java.lang.String |
as is (commas should be escaped with \ ) |
array type whose component type is one of the preceding types | joined by comma (, ) |
type satisfying Class.isAnnotation() |
omitted from available accessor properties |
array type whose component type satisfies Class.isAnnotation() |
omitted from available accessor properties |
It should be noted that if the resulting strings contain macro characters these will be interpreted by bnd’s macro engine. This may produce unexpected results due to potentially nested macros. In this scenario steps must be taken to either escape these characters or that nesting is done to allow bnd to successfully process the result.
The return type Class<?>
is particularly useful when it comes to making your code friendly to refactoring.
@Capability(
filter = "objectClass:List<String>='${#value}'",
namespace = "osgi.service",
)
@interface MyService {
Class<?>[] value();
}
// applied as
@MyService(Bar.class)
class Bar { ... }
What if you want the default to be the annotated class itself? Bnd made this possible by using a type which was unlikely to be a real argument. The type selected was java.lang.annotation.Target
because it is guaranteed to be accessible, is unlikely to be a real argument, will not require an additional dependency, and it’s nomenclature proved the most expressive for the use case.
@Capability(
filter = "objectClass:List<String>='${#value}'",
namespace = "osgi.service",
)
@interface MyService {
Class<?>[] value() default Target.class;
}
// applied as
@MyService
class Bar { ... }
Instances of java.lang.annotation.Target.class
found in the return value of Class<?>
or Class<?>[]
methods will be replaced by the annotated type prior to string conversion.
In order to control the cardinality of requirements generated by custom bundle annotations a convenience enum aQute.bnd.annotation.Cardinality
is available as a method return type. The default value should be Cardinality.DEFAULT
ensuring that if not set no directive will be emitted.
@Requirement(
attribute = {
aQute.bnd.annotation.Constants.CARDINALITY_MACRO
},
namespace = "osgi.service",
)
@interface RequireMyService {
Cardinality cardinality() default Cardinality.DEFAULT;
}
// applied as
@RequireMyService(cardinality = Cardinality.MULTIPLE)
class Bar { ... }
Note: It is recommended to use the macro constant aQute.bnd.annotation.Constants.CARDINALITY_MACRO
crafted specifically for handling the cardinality directive. It should also be noted that during macro processing the enum name()
will be converted to lower case.
In order to control the resolution of requirements generated by custom bundle annotations a convenience enum aQute.bnd.annotation.Resolution
is available as method return type. The default value should be Resolution.DEFAULT
ensuring that if not set no directive will be emitted.
@Requirement(
attribute = {
aQute.bnd.annotation.Constants.RESOLUTION_MACRO
},
namespace = "osgi.service",
)
@interface RequireMyService {
Resolution resolution() default Resolution.DEFAULT;
}
// applied as
@RequireMyService(resolution = Resolution.OPTIONAL)
class Bar { ... }
Note: It is recommended to use the macro constant aQute.bnd.annotation.Constants.RESOLUTION_MACRO
crafted specifically for handling the resolution directive. It should also be noted that during macro processing the enum name()
will be converted to lower case.
In order to control the effective time of a requirement generated by custom bundle annotations a method called effective
with a return type of String
can be used in conjunction with the macro constant aQute.bnd.annotation.Constants.EFFECTIVE_MACRO
. The default value should be the empty string (""
) ensuring that if not set no directive will be emitted.
@Requirement(
attribute = {
aQute.bnd.annotation.Constants.EFFECTIVE_MACRO
},
namespace = "osgi.service",
)
@interface RequireMyService {
String effective() default "";
}
// applied as
@RequireMyService(effective = "active")
class Bar { ... }
In order to control the uses constraints of a requirement generated by custom bundle annotations a method called uses
with a return type of Class<?>[]
can be used in conjunction with the macro constant aQute.bnd.annotation.Constants.USES_MACRO
. The default value should be an empty array ({}
) ensuring that if not set the directive will not be emitted.
@Requirement(
attribute = {
aQute.bnd.annotation.Constants.USES_MACRO
},
namespace = "osgi.service",
)
@interface RequireMyService {
Class<?>[] uses() default {};
}
// applied as
@RequireMyService(uses = {com.acme.Foo.class, org.bar.Bar.class})
class Bar { ... }
####