How to inject MessagesApi outside a controller? - java

I have a simple class service and it is being injected beautifully on my application. However, I am trying to inject the messages api to read a few keys on my message files but I am getting the same error:
1) Could not find a suitable constructor in play.i18n.Messages.
Classes must have either one (and only one) constructor annotated with
#Inject or a zero-argument constructor that is not private. at
play.i18n.Messages.class(Messages.java:61)
public class SampleServiceImpl implements SampleService {
private MessagesApi messages;
#Inject
public SampleServiceImpl(MessagesApi messages){
this.messages = messages;
}
}
#ImplementedBy(SampleServiceImpl.class)
public interface SampleService {
}
Is the a way to do that by DI?
Edit:
I was able to get the value by doing this but it does not look elegant, any options ?
messages.get(new Lang(new Locale("en")), "ticket.form.title")

The reason of such "non-elegancy" is that language (and Messages) depends on the request.
The Default behavior is that Messages detect current language on the base of cookie, available languages and default language.
Someware under the hood: Messages messages = messagesApi.preferred(request());
Will select a language from the request, based on the languages
available, and fallback to the default language if none of the
candidates are available.
Fortunately, there is a special method that you can use to initialize Messages with the language you want:
import play.i18n.MessagesApi;
import play.i18n.Messages;
import play.i18n.Lang;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
...
Locale englishLocale = new Locale("en");
Lang englishLang = new Lang(englishLocale);
List<Lang> prefferedLangs = Arrays.asList(englishLang);
Messages messagesCustom = messagesApi.preferred(prefferedLangs);
// the time for elegancy
messages.at("ticket.form.title");
I advise you to create tiny MessagesApiCustom service, that will do this few strings of code in the initialization time and then will proxy the at method to the messages.at, so it will look like:
public class SampleServiceImpl implements SampleService {
private MessagesApiCustom messages;
#Inject
public SampleServiceImpl(MessagesApiCustom messages){
this.messages = messages;
}
private void doSomeStuff(){
Strign message = messages.at("message.key")
}
}
You can go further, and implement language selection based on annotation:
#Named("FR")
private MessagesApiCustom messages;
Of course, if you need the dynamic language selection, then just use the method that is already present in Play.

Related

Replacing an annotation with another annotation during compile time in Spring?

I am using Swagger annotations over my controller parameters. So, I end up with annotations like #ApiParam(name="default name", value="this is a default value"). I think this is quite verbose. I would like to change it to something like #Foo. I want to know if there's a way to replace #Foo with #ApiParam during compile time. Also, since I am using Spring, I think I have to consider the annotation processing order in Spring, as well. I mean I shouldn't replace #ApiParam with #Foo after Swagger or Spring picks it up. Is there any way to do this?
In simpler words, I have the same annotation with the same parameters used 5 times. Basically, I want to replace them with some custom annotation.
I know I have to show what I have already tried, but I have no clue where to even start.
Also, the question is not related to Swagger, it is just an example. I want to replace one annotation with another during compile time, so that the one picked up by Spring won't be the one I have put on the source code, but the one I have replaced.
If I understand what you are asking for, this is possible without compile-time annotation processing. It's not pretty and it might be more complexity than it's worth, but here's one way to do it.
Here's a custom annotation I made that is used for my shorthand #ApiParam.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface GameIdParam {
String name() default "My Game ID";
String value() default "The integer ID of a particular game";
}
You can define whatever properties in #ApiParam that you wish to override. Then you can use Springfox's Extension Framework to implement a custom handler for the new annotation.
import com.google.common.base.Optional;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.Example;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.swagger.readers.parameter.ApiParamParameterBuilder;
import java.util.function.Predicate;
import static java.util.Optional.ofNullable;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.pluginDoesApply;
import static springfox.documentation.swagger.readers.parameter.Examples.examples;
#Component
public class ShorthandAnnotationPlugin extends ApiParamParameterBuilder {
private final DescriptionResolver descriptions;
private final EnumTypeDeterminer enumTypeDeterminer;
#Autowired
public ShorthandAnnotationPlugin(
DescriptionResolver descriptions,
EnumTypeDeterminer enumTypeDeterminer) {
super(descriptions, enumTypeDeterminer);
this.descriptions = descriptions;
this.enumTypeDeterminer = enumTypeDeterminer;
}
#Override
public void apply(ParameterContext context) {
Optional<GameIdParam> gameIdParam = context.resolvedMethodParameter().findAnnotation(GameIdParam.class);
if (gameIdParam.isPresent()) {
GameIdParam annotation = gameIdParam.get();
// Instantiate an ApiParam so we can take default values for attributes we didn't override.
ApiParam parentAnnotation = AnnotationUtils.synthesizeAnnotation(ApiParam.class);
context.parameterBuilder().name(ofNullable(annotation.name())
.filter(((Predicate<String>) String::isEmpty).negate()).orElse(null))
.description(ofNullable(descriptions.resolve(annotation.value()))
.filter(((Predicate<String>) String::isEmpty).negate()).orElse(null))
.parameterAccess(ofNullable(parentAnnotation.access())
.filter(((Predicate<String>) String::isEmpty).negate())
.orElse(null))
.defaultValue(ofNullable(parentAnnotation.defaultValue())
.filter(((Predicate<String>) String::isEmpty).negate())
.orElse(null))
.allowMultiple(parentAnnotation.allowMultiple())
.allowEmptyValue(parentAnnotation.allowEmptyValue())
.required(parentAnnotation.required())
.scalarExample(new Example(parentAnnotation.example()))
.complexExamples(examples(parentAnnotation.examples()))
.hidden(parentAnnotation.hidden())
.collectionFormat(parentAnnotation.collectionFormat())
.order(SWAGGER_PLUGIN_ORDER);
}
}
#Override
public boolean supports(DocumentationType documentationType) {
return pluginDoesApply(documentationType);
}
}
I used Springfox's ApiParamParameterBuilder as an example.
Now, I can use my #GameIdParam
#PostMapping("/{gameId}/info")
public String play(#GameIdParam #PathVariable int gameId) // ...
This pattern could be generalized to work with a series of custom shorthand annotations. It's not pretty and it introduces another level of indirection that people who know Springfox Swagger won't be familiar with.
Hope that helps! Good luck!

Dynamically injecting generic objects with guice

My current situation:
I want to inject the following class into my application:
public interface IConfigAccessor<T extends IConfig> {
...
}
ConfigAccessors are a proxy-objects, created dynamically at runtime. The creation of these object works as follows:
public class ConfigFactory implements IConfigFactory {
private final IConfigUpdater updater;
#Inject
public ConfigFactory(IConfigUpdater updater) {
this.updater = updater;
}
#Override
public <T extends IConfig> IConfigAccessor<T> register(final String configKey, final Class<T> configClass) {
ConfigCache<T> configCache = new ConfigCache<>(new SomeOtherThings(), configKey, configClass);
updater.register(configCache);
return new ConfigAccessor<>(configCache, configKey, configClass);
}
}
As you can see, to create these objects, I need to inject the ConfigUpdater and other depdencies. This means, that guice needs to be fully configured already.
To get the instance out of Guice, I use the following code:
IConfigFactory configClient = injector.getInstance(IConfigFactory.class);
IConfigAccessor<ConcreteConfig> accessor = configClient.register("key", ConcreteConfig.class)
How I want to inject them via Guice:
Currently, I can get the requried objects, but I have to manually pass them around in my application.
Instead, what I want to have is the following:
public class SomeClass {
#Inject
public SomeClass(#Config(configKey="key") IConfigAccessor<ConcreteConfig> accessor) {
// hurray!
}
}
What's the correct approach/technology to get this working?
After a lot of research, I'm feeling a bit lost on how to approach this topic. There are a lot of different things Guice offers, including simple Providers, custom Listeners which scan classes and identify custom annotations, FactoryModuleBuilders and more.
My problem is quite specific, and I'm not sure which of these things to use and how to get it working. I'm not even sure if this is even possible with Guice?
Edit: What I have so far
I have the following annotation which I want to use inside constructor paramters:
#Target({ ElementType.FIELD, ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface InjectConfig {
String configKey();
}
Inside the module, I can bind a provider to IConfigAccessor (with the above annotation) as such:
bind(IConfigAccessor.class).annotatedWith(InjectConfig.class)
.toProvider(new ConfigProvider<>());
However, there are two problems whith this:
The provider cannot provide IConfigAccessor. To create such an instance, the provider would need an IConfigUpdater, but since I use 'new' for the provider, I can't inject it.
Inside the provider, there is no way to find out about the configKey used in the Annotation.
Second approach:
Let's assume that I already know all configurations and configKeys I want to inject during startup. In this case, I could loop over all possible configKeys and have the following binding:
String configKey = "some key";
final Class<? extends IConfig> configClass =...;
bind(IConfigAccessor.class).annotatedWith(Names.named(configKey))
.toProvider(new ConfigProvider<>(configKey, configClass));
However, problem (1) still resides: The provider cannot get an IConfigUpdater instance.
The main problem here is that you cannot use the value of the annotation in the injection. There is another question which covers this part:
Guice inject based on annotation value
Instead of binding a provider instance, you should bind the provider class, and get the class by injecting a typeliteral.
That way, your config factory can look like that:
public class ConfigFactory<T extends IConfig> implements IConfigFactory {
#Inject private final IConfigUpdater updater;
#Inject private TypeLiteral<T> type;
#Override
public IConfigAccessor<T> register(final String configKey) {
Class<T> configClass = (Class<T>)type.getRawType();
ConfigCache<T> configCache = new ConfigCache<>(new SomeOtherThings(), configKey, configClass);
updater.register(configCache);
return new ConfigAccessor<>(configCache, configKey, configClass);
}
}
And then SomeClass:
public class SomeClass {
#Inject
public SomeClass(ConfigFactory<ConcreteConfig> accessor) {
ConcreteConfig config = accessor.register("key");
}
}
Since SomeClass needs to know "key" anyway, this is not too much a change information-wise. The downside is that the SomeClass API now gets a factory instead of the concrete config.
[EDIT]
And here is someone who actually did inject annotated values using custom injection.

#AroundInvoke interceptor is called twice on a #WebService class

Summary
#AroundInvoke interceptor is called twice on a #WebService class,
if the intercepted method is called from outside of the application via endpoint as a SOAP web service.
If the very same method is called internally from another bean, it's called only once (as I would expect).
The intercepted method itself is always called only once!
Question 1: Can I make the interceptor to be called only once?
Question 2: If I cannot, is there a transferable (server independent) way to decide in which interceptor I am, so I can ignore the redundant one?
Question 3: Is this behaviour common (and defined and described in some documentation),
or is it dependent on my specific environment (JBoss EAP 6.4.0)?
Observation:
The two calls are not in the same interceptor chain.
It is not the same instance of the interceptor class.
The implementation class of the InvocationContext is different for both the calls.
It's funny that one of the contextData, the InvocationContext's field for passing data along the interceptor chain, is not an instance of the HashMap, but WrappedMessageContext, but it does not wrap the other contextData anyway.
Minimal reproducible code
(I removed the package name.)
MyEndpoint interface
import javax.jws.WebService;
#WebService
public interface MyEndpoint {
public static final String SERVICE_NAME = "MyEndpointService";
public String getHello();
}
MyEndpointImpl class
import javax.interceptor.Interceptors;
import javax.jws.WebService;
#WebService(endpointInterface = "MyEndpoint", serviceName = MyEndpoint.SERVICE_NAME)
#Interceptors({TestInterceptor.class})
public class MyEndpointImpl implements MyEndpoint {
#Override
public String getHello() {
System.out.println("MyEndpointImpl.getHello() called");
return "Hello";
}
}
TestInterceptor class
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class TestInterceptor {
#AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
System.out.println("Interceptor called");
return ic.proceed();
}
}
Output
Interceptor called
Interceptor called
MyEndpointImpl.getHello() called
More details
To get more runtime information, I added more logging.
MyEndpointImpl class
import java.lang.reflect.Method;
import java.util.Map;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestInterceptor {
private static Logger logger = LoggerFactory.getLogger(TestInterceptor.class);
private static int callCnt = 0;
#AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
final String interceptorClass = this.toString();
final String invocationContextClass = ic.getClass().getName();
final Method method = ic.getMethod();
final String calledClass = method.getDeclaringClass().getName();
final String calledName = method.getName();
final String message = String.format(
"%n INTERCEPTOR: %s%n InvocationContext: %s%n %s # %s()",
interceptorClass, invocationContextClass, calledClass, calledName);
logger.info(message);
final int call = ++callCnt;
final Map<String, Object> contextData = ic.getContextData();
contextData.put("whoami", call);
logger.info("BEFORE PROCEED {}, {}", call, contextData);
final Object ret = ic.proceed();
logger.info("AFTER PROCEED {}, {}", call, contextData);
return ret;
}
}
Output
INTERCEPTOR: TestInterceptor#74c90b72
InvocationContext: org.jboss.invocation.InterceptorContext$Invocation
MyEndpointImpl # getHello()
BEFORE PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext#2cfccb1d
INTERCEPTOR: TestInterceptor#5226f6d8
InvocationContext: org.jboss.weld.interceptor.proxy.InterceptorInvocationContext
MyEndpointImpl # getHello()
BEFORE PROCEED 2, {whoami=2}
MyEndpointImpl.getHello() called
AFTER PROCEED 2, {whoami=2}
AFTER PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext#2cfccb1d
I cannot answer your questions directly, but maybe some clarification about the contexts may help you.
The Java EE JAX-WS implementation varies from server to server. For Example Glassfish uses Metro and JBoss uses Apache CXF.
There are different kind of interceptors chains which allow to control programmatically the conditions before and after the request/response processing.
The interceptors for the SOAP web service calls are SOAP handlers and logical handlers (See Oracle documentation). Both can access SOAP message on different levels (the whole or only the payload).
My assumption is that your the interceptor called twice, once for accessing through HTTP/SOAP, and once for access over RMI.
In the first interceptor invocation, what you see as context is org.apache.cxf.jaxws.context.WrappedMessageContext which is a Map implementation. See WarppedMessageContext, Apache CXF web service context. It is invoked for HTTP/SOAP access.
The second invocation is what you expect when using the RMI (probably triggered from Apache CXF, after the SOAP message is processed).
To avoid this you can use third class for logic implementation with interceptor defined. The existing web service implementation class will only delegate to it and will not contain interceptor annotation anymore.
Example code can be seen here: OSCM Project
I had the exact same problem and found a solution.
If you instead of using the #Interceptors style binding use a #InterceptorBinding style binding, then the interceptor is only instantiated and invoked once (at least in my case on WildFly 10.1.0.Final).
This is what your example would look like using #InterceptorBinding style.
Your custom interceptor binding annotation:
import javax.interceptor.InterceptorBinding;
...
#Inherited
#InterceptorBinding
#Retention(RUNTIME)
#Target({METHOD, TYPE})
public #interface MyInterceptorBinding {
Your endpoint:
#WebService(endpointInterface = "MyEndpoint", serviceName =
MyEndpoint.SERVICE_NAME)
#MyInterceptorBinding
public class MyEndpointImpl implements MyEndpoint {
Your interceptor:
import javax.interceptor.Interceptor;
import javax.annotation.Priority;
...
#Interceptor
#MyInterceptorBinding
#Priority(Interceptor.Priority.APPLICATION) //we use #Priority to enable this interceptor application-wide, without having to use beans.xml in every module.
public class TestInterceptor {
#AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
System.out.println("Interceptor called");
return ic.proceed();
}
I never figured out exactly what the problem is, but I suspect that the #Interceptors style binding is valid for multiple types of interceptors (EJB and CDI) while the #InterceptorBinding style is maybe only valid for CDI interceptors.
Maybe a JAX-WS #WebService is both an EJB and a CDI bean?

Dropwizard + Jersey : "Not inside a request scope" when creating custom annotation

I have a simple Dropwizard 0.8.1 REST service that pulls in Jersey 2.17. Upstream of the REST/Jetty service I have some authentication service that adds some nice authorization information to the HTTP Header that gets passed to my Dropwizard app.
I would love to be able to create a custom annotation in my Resource that hides all the messy header-parsing-to-POJO garbage. Something like this:
#Path("/v1/task")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class TaskResource {
#UserContext // <-- custom/magic annotation
private UserContextData userContextData; // <-- holds all authorization info
#GET
public Collection<Task> fetch() {
// use the userContextData to differentiate what data to return
}
I've spent the last day looking around stackoverflow and found several other people who had the same issue and appeared (?) to get some satisfaction, but I can't seem to avoid getting a "Not inside a request scope" stack trace when I try to do this.
So I stashed all my changes and tried to implement the example provided in sections 22.1 and 22.2 by the Jersey documentation directly: https://jersey.java.net/documentation/2.17/ioc.html
Following along with their example (but in my Dropwizard app), I'm trying to get a "#SessionInject" annotation in my Resource, but it also blows up with "Not inside a request scope" stack trace each time. What am I doing wrong here?
Resource:
#Path("/v1/task")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class TaskResource {
private final TaskDAO taskDAO;
#Context
private HttpServletRequest httpRequest;
#SessionInject
private HttpSession httpSession;
public TaskResource(TaskDAO taskDAO) {
this.taskDAO = taskDAO;
}
#GET
public Collection<Task> fetch(#SessionInject HttpSession httpSession) {
if (httpSession != null) {
logger.info("TOM TOM TOM httpSession isn't null: {}", httpSession);
}
else {
logger.error("TOM TOM TOM httpSession is null");
}
return taskDAO.findAllTasks();
}
The SessionInjectResolver:
package com.foo.admiral.integration.jersey;
import com.foo.admiral.integration.core.SessionInject;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SessionInjectResolver implements InjectionResolver<SessionInject> {
private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);
#Inject
#Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
InjectionResolver<Inject> systemInjectionResolver;
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (HttpSession.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, handle);
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
}
The HttpSessionFactory:
package com.foo.admiral.integration.jersey;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Singleton
public class HttpSessionFactory implements Factory<HttpSession> {
private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);
private final HttpServletRequest request;
#Inject
public HttpSessionFactory(HttpServletRequest request) {
logger.info("Creating new HttpSessionFactory with request");
this.request = request;
}
#Override
public HttpSession provide() {
logger.info("Providing a new session if one does not exist");
return request.getSession(true);
}
#Override
public void dispose(HttpSession t) {
}
}
The annotation:
package com.foo.admiral.integration.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD})
public #interface SessionInject {
}
And, finally, the binding in the Dropwizard Application class:
#Override
public void run(TodoConfiguration configuration, Environment environment) throws Exception {
...
environment.jersey().register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(HttpSessionFactory.class).to(HttpSession.class);
bind(SessionInjectResolver.class)
.to(new TypeLiteral<InjectionResolver<SessionInject>>() { })
.in(Singleton.class);
}
});
Ye old stack trace:
Caused by: java.lang.IllegalStateException: Not inside a request scope.
at jersey.repackaged.com.google.common.base.Preconditions.checkState(Preconditions.java:149)
at org.glassfish.jersey.process.internal.RequestScope.current(RequestScope.java:233)
at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:158)
at org.jvnet.hk2.internal.MethodInterceptorImpl.invoke(MethodInterceptorImpl.java:74)
at org.jvnet.hk2.internal.MethodInterceptorInvocationHandler.invoke(MethodInterceptorInvocationHandler.java:62)
at com.sun.proxy.$Proxy72.getSession(Unknown Source)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:29)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:14)
Some clues that may be useful:
1) I'm noticing is that the logging statements in my HttpSessionFactory are never getting fired, so I don't think the Factory is correctly identified to DropWizard.
2) If I change the annotation to be a Parameter instead of a Field and move the use of the annotation into the fetch( ) method signature like this, it doesn't throw the stack trace (but the httpSession is still null, presumably because the Factory isn't firing...)
public Collection<Task> fetch(#SessionInject HttpSession httpSession) {
3) It doesn't appear to matter if I "register" the binder with environment.jersey().register() or environment.jersey().getResourceConfig().register()... they appear to do the same thing.
Do you see any obvious problems? Thanks in advance!
This is weird behavior. But what looks like is going on is the following
You have registered TaskResource as an instance and not as a .class. This I'm pretty sure of (though you have not mentioned).
register(new TaskResource());
/* instead of */
register(TaskResource.class);
Doing the former, it set the resource in a singleton scope. The latter in a request scope (unless annotated otherwise - see below)
When the resource model is loading it sees the TaskResource is a singleton, and that the HttpServletRequest is in a request scope. Either that or that the factory is in a per request scope. I'm guessing one of the two.
I thought that it might actually be a scope issue, as mentioned in the error message, but what I'm pretty sure of is that at runtime, it will get handled with a thread local proxy, because of the lesser scope.
You can see it fixed by registering the TaskResource as a class, and then annotating the TaskResource with #Singleton. This is if you actually do want the resource class to be a singleton. If not, then just leave off the #Singleton.
The odd thing to me is that it the fact that it fails on startup when the resource is explicitly instantiated on startup, but works when the framework loads on the first request (which is what happens when you register it as a class). They are both still in a singleton scope.
One thing you might want to take into consideration is whether you actually want the resource to be a singleton or not. You do have to worry about thread safety issues with singletons, and there are are some other limitations. Personally, I prefer to keep them in a request scope. You would have to do some performance testing to see if there is much of an impact for your application.
UPDATE
For parameter injection you may want to take a look at this post
UPDATE 2
See Also
jersey 2 context injection based upon HttpRequest without singleton. My answer should shed some more light.

EJB Factory Class

I'm trying to create an EJB factory class, which works like this: You have a method which takes as argument a class of an EJB, then it checks whether the EJB has a remote interface (if not throw an exception) and if it does, it returns the concerning EJB.
The code below does exactly this. However the object it returns is of the type of the remote interface of the concerning bean and not of the bean itself. How can I change this? Is there a way to tell Java that the generic type T is of the same type as the class passed to the methods.
import java.util.Properties;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.naming.*;
public class EJBFactory
{
private InitialContext ctx;
public EJBFactory() throws NamingException {
ctx = new InitialContext();
}
public EJBFactory(String host, String port) throws NamingException {
Properties props = new Properties();
props.setProperty("org.omg.CORBA.ORBInitialHost", host);
props.setProperty("org.omg.CORBA.ORBInitialPort", port);
ctx = new InitialContext(props);
}
.
// To improve: The object returned should be of the type ejbClass
// instead of the remote interface, which it implements
public <T> T createEJB(Class ejbClass) throws NamingException
{
Class remoteInterface = null;
for(Class interface_: ejbClass.getInterfaces()) {
if(interface_.isAnnotationPresent(Remote.class))
remoteInterface = interface_;
}
if(remoteInterface == null)
throw new IllegalArgumentException(
"EJB Requires a remote interface");
// Get the stateless annotation, then get the jndiName
Stateless stateless =
(Stateless)ejbClass.getAnnotation(Stateless.class);
String jndiName = stateless.mappedName();
T ejbObj = (T) ctx.lookup(jndiName);
return ejbObj;
}
}
Example of a unit test which uses the Factory.
import junit.framework.TestCase;
public class SimpleEJBTest extends TestCase
{
TestRemote testBean;
#Override
protected void setUp() throws Exception {
super.setUp();
EJBFactory ejbFactory = new EJBFactory();
testBean = ejbFactory.createEJB(TestBean.class);
}
public void testSayHello() {
assertEquals("Hello", testBean.sayHello());
}
}
Note: The example works with Glassfish, I didn't test it with any other app server.
Clients of EJBs interact with them through the local/ remote interface that the EJBs implement. Client applications never have direct access to an actual session bean class instance. This is done to make instance pooling possible, where the container can reuse EJB instances to service different requests.
I'm not sure why you need to access the actual bean's object (since obviously I dont know your requirement). But if you still need to create an instance of that you can do it as follows using reflection Class.forName(className).newInstance(); Again the instance that you create like this is not an EJB. It is just a POJO thats all.
EDIT - after your comment regarding junit testing: When you access business methods from a JavaSE as follows, you are actually calling the methods in the EJB - just that you interact thru the interface. So if you want to test any business methods you can still do it from an object got thru a JNDI lookup in a Junit test.
//MyGreatBean implements MyGreat. MyGreat has #Remote, MyGreatBean has #Stateless
ref = jndiContext.lookup("MyGreatBean/remote");
MyGreat bean = (MyGreat) ref;
String retValue = bean.businessMethod();
assertEquals("Success", retValue);
From an earlier comment, I get a feeling you want to check what kind of annotations have been added to the actual EJB class - if you want to do that kind of checking without actually running the business methods, you can create an instance using Class.forName... like I mentioned above. When you create an instance like this you can only call methods that don't do any "Java EE" stuff. For example you can call a method in the EJB class that is as follows
public String someMethod(){
return "I am a POJO but I look like an EJB";
}
I do not think that you can get the EJB object. You can only get the interface. The createEJB should be called with the interface and it returns the interface.
try replacing
public <T> T createEJB(Class ejbClass) throws NamingException
with
public <T> T createEJB(Class<T> ejbClass) throws NamingException
Can you try this?
Create a interface. Make it have #Remote. Your ejb that is annotated with #Stateless should implement the above created interface. Now try to do the same thing that you are doing I think it should give you the desired result. Typing it down here without copying from an ide so excuse any errors. But you should get the drift I guess.
#Remote
public interface Example{
String some();
}
#stateless
public class ExampleBean implements Example{
}

Categories