Kind of a follow up to my previous question. I'm trying to inject application configuration data using JSR-330 standard annotations and the HK2 framework bundled with jersey.
Ideally I'd like to create a custom InjectionResolver
for the Named
annotation, which will lookup the desired values in a Map
or Properties
object that I will populate from data read elsewhere. In my first attempt I've created an Application
instance like
public class MyApplication extends ResourceConfig {
...
packages(MY_PACKAGES);
property(MY_CONFIG_PROPERTY, someValue);
register(new AbstractBinder() {
@Override
protected void configure() {
bind(ConfigurationInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Named>>(){})
.in(Singleton.class)
}
});
}
and then my InjectionResolver
looks like
public class ConfigurationInjectionResolver implements InjectionResolver<Named> {
@Context Application application;
@Override
public Object resolve(Injectee injectee, ServiceHandle<?> serviceHandle) {
// lookup data in application.getProperties();
}
}
My problem is that application.getProperties()
is empty. Any idea what's wrong? Also, could I bind an instance of my Injector instead of binding the class? That way I could construct the instance passing my Map
data as a parameter.
"My problem is that application.getProperties() is empty. Any idea what's wrong?
No. This actually works perfectly fine for me.
public class ConfigurationInjectionResolver implements InjectionResolver<Named> {
@Context
Application application;
@Override
public Object resolve(Injectee injectee, ServiceHandle<?> root) {
Named annotation = injectee.getParent().getAnnotation(Named.class);
Map<String, Object> props = application.getProperties();
String name = annotation.value();
System.out.println(props.get(name));
return props.get(name);
}
@Override
public boolean isConstructorParameterIndicator() { return false; }
@Override
public boolean isMethodParameterIndicator() { return false; }
}
@ApplicationPath("/rest")
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
packages("jersey.startup.test");
property("hello.config", "Hello World Property");
register(new AbstractBinder() {
@Override
protected void configure() {
bind(ConfigurationInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Named>>() {
}).in(Singleton.class);
}
});
}
}
Resource
@Path("/config")
public class ConfigResource {
@Named("hello.config")
String hello;
@GET
public Response getHello() {
return Response.ok(hello).build();
}
}
C:\>curl http://localhost:8080/test/rest/config
Hello World Property
Personally though, in this situation, I would create my own annotation, as to not override any existing functionality of the @Named
annotation.
Another cool option
HK2 has a configuration extension, where you can load a Properties
object from say a .properties
file and and have those properties automatically injected with the @Configured
annotation. I couldn't find any documentation on this, but there is an example usage of it in the HK2 source code examples.
Here's an example implementation
Required dependencies. Check the Jersey version and see what HK2 version it depends on. In my case Jersey 2.13 uses HK2 2.3.0-b10, so that should be the ${hk2.version}
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-configuration-hub</artifactId>
<version>${hk2.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-configuration-integration</artifactId>
<version>${hk2.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-property-file</artifactId>
<version>${hk2.version}</version>
</dependency>
App config
@ApplicationPath("/rest")
public class JerseyApplication extends ResourceConfig {
@Inject
public JerseyApplication(ServiceLocator locator) {
packages("jersey.startup.test");
ServiceLocatorUtilities.addClasses(locator, ConfigResource.class);
try {
loadConfigurationProperties(locator);
} catch (IOException ex) {
Logger.getLogger(JerseyApplication.class.getName())
.log(Level.SEVERE, null, ex);
}
}
private void loadConfigurationProperties(ServiceLocator locator)
throws IOException {
ConfigurationUtilities.enableConfigurationSystem(locator);
PropertyFileUtilities.enablePropertyFileService(locator);
PropertyFileService propertyFileService
= locator.getService(PropertyFileService.class);
Properties props = new Properties();
URL url = getClass().getResource("/configuration.properties");
props.load(url.openStream());
PropertyFileHandle propertyFileHandle
= propertyFileService.createPropertyHandleOfAnyType();
propertyFileHandle.readProperties(props);
}
}
configuration.properties
AppConfiguration.App.hello=Hello Squirrel Property!
Resource
@Path("/config")
@ConfiguredBy("AppConfiguration")
public class ConfigResource {
@Configured
String hello;
@GET
public Response getHello() {
return Response.ok(hello).build();
}
}
C:\>curl http://localhost:8080/test/rest/config
Hello Squirrel Property!
Diclaimer: Since this feature isn't well documented, I am not sure if I have a good implementation here. It is just by trial and error. For instance this
ServiceLocatorUtilities.addClasses(locator, ConfigResource.class);
I feel shouldn't be necessary. It seems redundant, as I am already package scanning. So to explicitly add the ConfigResource
to the locator context doesn't seem right to me.