We use EJB3 and JBOSS application Server in our Application. I have a Bean lookup utility method where its a generic method written to lookup stateless EJB Beans by JNDI name:
public class BeanFactory {
static Logger logger = LogManager.getLogger(BeanFactory.class.getName());
/**
*
* #param jndiName
* #return
*/
public static <T> T lookup(String jndiName){
logger.info("Inside bean BeanFactory lookup: " + jndiName);
T handle = null;
try {
InitialContext ctx = new InitialContext();
handle = (T) ctx.lookup(jndiName);
} catch (Exception e) {
logger.error(e, e.fillInStackTrace());
}
return handle;
}
So there are classes which have dependencies on Beans and they use lookup method to invoke the methods of the Bean. For Example
private AuthenticationProfileDTO getAuthenticationProfile(String credId) throws OneM2MException {
ResourceProceduresDao dao = BeanFactory.lookup(ResourceProceduresDao.JNDI_NAME);
AuthenticationProfileRemote apRemote = BeanFactory.lookup(AuthenticationProfileRemote.JNDI_NAME);
AuthenticationProfileDTO authenticationProfileDTO;
if (isKpsaId(credId))
authenticationProfileDTO = apRemote.getAuthenticationProfileDTOForSymmKeyID(credId);
else
authenticationProfileDTO = apRemote.getAuthenticationProfileDTOForCredentialID(credId);
return authenticationProfileDTO;
}
So now when we ran JProfiler on the code the lookup method is coming to be time consuming because every time lookup is called a new InitialContext is instantiated.
I was thinking of making the InitialContext static so that only once it's initialized in a static block, but I don't know what implications will it have in terms of getting Bean instances. Since this piece of code is managed by EJB Container, the run time impacts are unknown. After looking up some articles online not much clarity was there.
Any help is appreciated.
Note that javadoc for InitialContext warns that:
An InitialContext instance is not synchronized against concurrent
access by multiple threads. Multiple threads each manipulating a
different InitialContext instance need not synchronize.
Threads that need to access a single InitialContext instance
concurrently should synchronize amongst themselves and provide the
necessary locking.
So, making the field static isn't necessarily a good idea as you'll need to synchronize each lookup(jndiName) call, and this may cause other issues as per comment by James R. Perkins.
However as you have shown that getAuthenticationProfile(String credId) calls lookup twice, there is no reason why you can't make a BeanFactory hold one InitialContext to reduce the number of instances by re-using InitialContext within same calling methods.
public class BeanFactory {
private final InitialContext ctx;
private BeanFactory(InitialContext initialContext) {
this.ctx = initialContext;
}
private static final Logger logger = LogManager.getLogger(BeanFactory.class.getName());
/** JNDI lookup*/
public <T> T lookup(String jndiName){
// logger.info("Inside bean BeanFactory lookup: " + jndiName);
try {
return (T) ctx.lookup(jndiName);
} catch (Exception e) {
RuntimeException re = new RuntimeException("Could not find jndi: "+jndiName, e);
logger.error(re);
throw re;
}
}
/** Setup a new BeanFactory */
public static BeanFactory create() {
try {
return new BeanFactory(new InitialContext());
} catch (Exception e) {
throw new RuntimeException("Could not create a new context", e);
logger.error(re);
throw re;
}
}
This allows getAuthenticationProfile to use a single InitialContext for 2 lookups:
BeanFactory ctx = BeanFactory.create();
ResourceProceduresDao dao = ctx.lookup(ResourceProceduresDao.JNDI_NAME);
AuthenticationProfileRemote apRemote = ctx.lookup(AuthenticationProfileRemote.JNDI_NAME);
You might also consider whether saving BeanFactory as a thread local would help though I would be very concerned about doing this an application server because you may have little control over which and how many threads instantiate InitialContext and what from what context they run. However it might be suitable within a standalone client program accessing your EJB server logic:
private static final ThreadLocal<BeanFactory> BEANS = ThreadLocal.withInitial(BeanFactory::create);
private static BeanFactory local() {
return BEANS.get();
}
// Example lookups:
ResourceProceduresDao dao = BeanFactory.local().lookup(ResourceProceduresDao.JNDI_NAME);
AuthenticationProfileRemote apRemote = BeanFactory.local().lookup(AuthenticationProfileRemote.JNDI_NAME);
when I try to mock following method(Method is using remote EJB call for business logic) for the Junit test, it gives javax.naming.NoInitialContextException
private void someMethod(int id1, int id2, HashMap map){
......some code........
Context ctx = new InitialContext();
Object ref = ctx.lookup("com.java.ejbs.MyEJB");
EJBHome ejbHome = (EJBHome)PortableRemoteObject.narrow(ref, EJBHome.class);
EJBBean ejbBean = (EJBBean)PortableRemoteObject.narrow(ejbHome.create(), EJBBean.class);
ejbBean.someMethod(id1,name);
.......some code.......}
My unit test for above method
#Test
public void testsomeMethod() throws Exception {
.......setting initial code...
//Mock context and JNDI
InitialContext cntxMock = PowerMock.createMock(InitialContext.class);
PowerMock.expectNew(InitialContext.class).andReturn(cntxMock);
expect(cntxMock.lookup("com.java.ejbs.MyEJB")).andReturn(refMock);
..........some code..........
PowerMock.replayAll();
Whitebox.invokeMethod(ObjectOfsomeMethodClass, "someMethod", id1, id2, map);
}
when the Whitebox.invokeMethod(ObjectOfsomeMethodClass, "someMethod", id1, id2, map) method invokes it gives following exception.
javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:645)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:325)
at javax.naming.InitialContext.lookup(InitialContext.java:392)
I believe, although we mock the Context in test method, it does not use the mock object when calling Whitebox.invokeMethod(ObjectOfsomeMethodClass, "someMethod", id1, id2, map) method, instead of that its trying to invoke the Context ctx = new InitialContext(); method in original method(someMethod).
Handmade
As InitialContext doc says, you can provide your own factory for InitialContext objects, using java.naming.factory.initial system property. When the code runs inside application server, the system property is set by the server. In our tests, we provide our own implementation of JNDI.
Here's my Mockito only solution: I defined a custom InitialContextFactory class, that returns a mock of InitialContext. You customize the mock as you wish, probably to return more mocks on lookup calls.
public class PlainTest {
#Mock InitialContextFactory ctx;
#InjectMocks Klasa1 klasa1;
public static class MyContextFactory implements InitialContextFactory
{
#Override
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
ConnectionFactory mockConnFact = mock(ConnectionFactory.class);
InitialContext mockCtx = mock(InitialContext.class);
when(mockCtx.lookup("jms1")).thenReturn(mockConnFact);
return mockCtx;
}
}
#Before
public void setupClass() throws IOException
{
MockitoAnnotations.initMocks(this);
System.setProperty("java.naming.factory.initial",
this.getClass().getCanonicalName() + "$MyContextFactory");
}
Spring (added by edit)
If you don't mind leveraging Spring Framework for testing purposes, here's their simple solution: SimpleNamingContextBuilder:
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
DataSource ds = new DriverManagerDataSource(...);
builder.bind("java:comp/env/jdbc/myds", ds);
builder.activate();
It's ok to put it in #Before or #BeforeClass. After activate(), jndi data will be pulled from spring dummy.
Adding to Jarekczek's answer (Thanks for it!!). Though it is an old question I would like to share my version of it in case it helps someone. I faced the same problem and one might just want to mock IntialContext only in a IntialContextFactory implementation class and it would be a better idea to use this mocked object in other tests or base test classes to avoid duplication.
public class MyContextFactory implements InitialContextFactory {
// Poor Singleton approach. Not thread-safe (but hope you get the idea)
private static InitialContext mockInitialContext;
#Override
public Context getInitialContext(Hashtable<?,?> hshtbl) throws NamingException {
if(mockInitialContext == null) {
mockInitialContext = mock(InitialContext.class);
}
return mockInitialContext;
}
}
public class TestClass {
private DataSource mockDataSource;
private Connection mockConnection;
protected void mockInitialContext() throws NamingException, SQLException {
System.setProperty("java.naming.factory.initial", "com.wrapper.MyContextFactory");
InitialContext mockInitialContext = (InitialContext) NamingManager.getInitialContext(System.getProperties());
mockDataSource = mock(DataSource.class);
mockConnection = mock(Connection.class);
when(mockInitialContext.lookup(anyString())).thenReturn(mockDataSource);
when(mockDataSource.getConnection()).thenReturn(mockConnection);
try {
when(mockDataSource.getConnection()).thenReturn(mockConnection);
} catch (SQLException ex) {
Logger.getLogger(CLASSNAME).log(Level.SEVERE, null, ex);
}
}
}
Reason behind taking this approach being if someone wants to use DataSource or any other resource provided by JNDI in a different way for different tests, you can follow this approach. There shall be just one instance created for IntialContext unless a multi-threaded test tries to access it simultaneously (don't know why would one try to do that!). That instance can be used in all places to get JNDI objects you want and use them as you want.
Hope this helps!
"Make sure you wash your hands before every meal and avoid System.out.println while debugging for healthy lifestyle"
You can refactor your code and extract the initialization of the context in new method.
private void someMethod(int id1, int id2, HashMap map){
......some code........
Context ctx = getInitialContext();
Object ref = ctx.lookup("com.java.ejbs.MyEJB");
EJBHome ejbHome = (EJBHome)PortableRemoteObject.narrow(ref, EJBHome.class);
EJBBean ejbBean = (EJBBean)PortableRemoteObject.narrow(ejbHome.create(), EJBBean.class);
ejbBean.someMethod(id1,name);
.......some code.......}
Your test code will be something like this:
Context mockContext = mock(Context.class);
doReturn(mockContext).when(yourclass).getInitalContext();
...... some code....
As of now (PowerMock 1.7.4)
Create a mock using PowerMockito.mock(InitialContext.class) rather than PowerMockito.createMock(InitialContext.class)
#Test
public void connectTest() {
String jndi = "jndi";
InitialContext initialContextMock = PowerMockito.mock(InitialContext.class);
ConnectionFactory connectionFactoryMock = PowerMockito.mock(ConnectionFactory.class);
PowerMockito.whenNew(InitialContext.class).withNoArguments().thenReturn(initialContextMock);
when(initialContextMock.lookup(jndi)).thenReturn(connectionFactoryMock);
...
// Your asserts go here ...
}
Do not create the InitialContext manually but let PowerMock do it for you. Also do not create a spy in which PowerMock expects an object. This means that you need to create the InitialContext instance.
Define the following Custom Classes
public static class CustomInitialContext extends InitialContext {
Hashtable<String, Object> ic = new Hashtable<>();
public CustomInitialContext() throws NamingException {
super(true);
}
public void bind(String name, Object object) {
ic.put(name, object);
}
public Object lookup(String name) throws NamingException {
return ic.get(name);
}
}
public static class CustomInitialContextFactory implements InitialContextFactory {
static InitialContext ic;
public CustomInitialContextFactory() {
if (ic == null) {
try {
ic = new CustomInitialContext();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
public Context getInitialContext(Hashtable<?, ?> arg0) throws NamingException {
return ic;
}
}
public static class CustomInitialContextFactoryBuilder implements InitialContextFactoryBuilder {
#Override
public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException {
return new CustomInitialContextFactory();
}
}
and declare factory as
NamingManager.setInitialContextFactoryBuilder(new CustomInitialContextFactoryBuilder());
I have several beans in my Spring context that have state, so I'd like to reset that state before/after unit tests.
My idea was to add a method to a helper class which just goes through all beans in the Spring context, checks for methods that are annotated with #Before or #After and invoke them.
How do I get a list of instantiated beans from the ApplicationContext?
Note: Solutions which simply iterate over all defined beans are useless because I have many lazy beans and some of them must not be instantiated because that would fail for some tests (i.e. I have a beans that need a java.sql.DataSource but the tests work because they don't need that bean).
For example:
public static List<Object> getInstantiatedSigletons(ApplicationContext ctx) {
List<Object> singletons = new ArrayList<Object>();
String[] all = ctx.getBeanDefinitionNames();
ConfigurableListableBeanFactory clbf = ((AbstractApplicationContext) ctx).getBeanFactory();
for (String name : all) {
Object s = clbf.getSingleton(name);
if (s != null)
singletons.add(s);
}
return singletons;
}
I had to improve it a little
#Resource
AbstractApplicationContext context;
#After
public void cleanup() {
resetAllMocks();
}
private void resetAllMocks() {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
for (String name : context.getBeanDefinitionNames()) {
Object bean = beanFactory.getSingleton(name);
if (Mockito.mockingDetails(bean).isMock()) {
Mockito.reset(bean);
}
}
}
I am not sure whether this will help you or not.
You need to create your own annotation eg. MyAnnot.
And place that annotation on the class which you want to get.
And then using following code you might get the instantiated bean.
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(MyAnnot.class));
for (BeanDefinition beanDefinition : scanner.findCandidateComponents("com.xxx.yyy")){
System.out.println(beanDefinition.getBeanClassName());
}
This way you can get all the beans having your custom annotation.
applicationContext.getBeanDefinitionNames() does not show the beans which are registered without BeanDefinition instance.
package io.velu.core;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan
public class Core {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Core.class);
String[] singletonNames = context.getDefaultListableBeanFactory().getSingletonNames();
for (String singleton : singletonNames) {
System.out.println(singleton);
}
}
}
Console Output
environment
systemProperties
systemEnvironment
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
messageSource
applicationEventMulticaster
lifecycleProcessor
As you can see in the output, environment, systemProperties, systemEnvironment beans will not be shown using context.getBeanDefinitionNames() method.
Spring Boot
For spring boot web applications, all the beans can be listed using the below endpoint.
#RestController
#RequestMapping("/list")
class ExportController {
#Autowired
private ApplicationContext applicationContext;
#GetMapping("/beans")
#ResponseStatus(value = HttpStatus.OK)
String[] registeredBeans() {
return printBeans();
}
private String[] printBeans() {
AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
if (autowireCapableBeanFactory instanceof SingletonBeanRegistry) {
String[] singletonNames = ((SingletonBeanRegistry) autowireCapableBeanFactory).getSingletonNames();
for (String singleton : singletonNames) {
System.out.println(singleton);
}
return singletonNames;
}
return null;
}
}
[
"autoConfigurationReport",
"springApplicationArguments",
"springBootBanner",
"springBootLoggingSystem",
"environment",
"systemProperties",
"systemEnvironment",
"org.springframework.context.annotation.internalConfigurationAnnotationProcessor",
"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory",
"org.springframework.boot.autoconfigure.condition.BeanTypeRegistry",
"org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry",
"propertySourcesPlaceholderConfigurer",
"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.store",
"preserveErrorControllerTargetClassPostProcessor",
"org.springframework.context.annotation.internalAutowiredAnnotationProcessor",
"org.springframework.context.annotation.internalRequiredAnnotationProcessor",
"org.springframework.context.annotation.internalCommonAnnotationProcessor",
"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor",
"org.springframework.scheduling.annotation.ProxyAsyncConfiguration",
"org.springframework.context.annotation.internalAsyncAnnotationProcessor",
"methodValidationPostProcessor",
"embeddedServletContainerCustomizerBeanPostProcessor",
"errorPageRegistrarBeanPostProcessor",
"messageSource",
"applicationEventMulticaster",
"org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration$EmbeddedTomcat",
"tomcatEmbeddedServletContainerFactory",
"org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration$TomcatWebSocketConfiguration",
"websocketContainerCustomizer",
"spring.http.encoding-org.springframework.boot.autoconfigure.web.HttpEncodingProperties",
"org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration",
"localeCharsetMappingsCustomizer",
"org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration",
"serverProperties",
"duplicateServerPropertiesDetector",
"spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration",
"conventionErrorViewResolver",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration",
"errorPageCustomizer",
"servletContext",
"contextParameters",
"contextAttributes",
"spring.mvc-org.springframework.boot.autoconfigure.web.WebMvcProperties",
"spring.http.multipart-org.springframework.boot.autoconfigure.web.MultipartProperties",
"org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration",
"multipartConfigElement",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletConfiguration",
"dispatcherServlet",
"dispatcherServletRegistration",
"requestContextFilter",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration",
"hiddenHttpMethodFilter",
"httpPutFormContentFilter",
"characterEncodingFilter",
"org.springframework.context.event.internalEventListenerProcessor",
"org.springframework.context.event.internalEventListenerFactory",
"reportGeneratorApplication",
"exportController",
"exportService",
"org.springframework.boot.autoconfigure.AutoConfigurationPackages",
"org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration",
"spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties",
"standardJacksonObjectMapperBuilderCustomizer",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration",
"jsonComponentModule",
"jacksonObjectMapperBuilder",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration",
"jacksonObjectMapper",
"org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration",
"org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration",
"org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration",
"defaultValidator",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration",
"error",
"beanNameViewResolver",
"errorAttributes",
"basicErrorController",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter",
"mvcContentNegotiationManager",
"org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration",
"stringHttpMessageConverter",
"org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration",
"mappingJackson2HttpMessageConverter",
"org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration",
"messageConverters",
"mvcConversionService",
"mvcValidator",
"requestMappingHandlerAdapter",
"mvcResourceUrlProvider",
"requestMappingHandlerMapping",
"mvcPathMatcher",
"mvcUrlPathHelper",
"viewControllerHandlerMapping",
"beanNameHandlerMapping",
"resourceHandlerMapping",
"defaultServletHandlerMapping",
"mvcUriComponentsContributor",
"httpRequestHandlerAdapter",
"simpleControllerHandlerAdapter",
"handlerExceptionResolver",
"mvcViewResolver",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration",
"faviconRequestHandler",
"faviconHandlerMapping",
"defaultViewResolver",
"viewResolver",
"welcomePageHandlerMapping",
"org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration",
"objectNamingStrategy",
"mbeanServer",
"mbeanExporter",
"org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration",
"springApplicationAdminRegistrar",
"org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration",
"org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration",
"spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties",
"org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration",
"multipartResolver",
"org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration$RestTemplateConfiguration",
"restTemplateBuilder",
"org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration",
"spring.devtools-org.springframework.boot.devtools.autoconfigure.DevToolsProperties",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$RestartConfiguration",
"fileSystemWatcherFactory",
"classPathRestartStrategy",
"classPathFileSystemWatcher",
"hateoasObjenesisCacheDisabler",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration$LiveReloadServerConfiguration",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration",
"optionalLiveReloadServer",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration",
"lifecycleProcessor"
]
I've created a gist ApplicationContextAwareTestBase.
This helper class does two things:
It sets all internal fields to null. This allows Java to free memory that isn't used anymore. It's less useful with Spring (the Spring context still keeps references to all the beans), though.
It tries to find all methods annotated with #After in all beans in the context and invokes them after the test.
That way, you can easily reset state of your singletons / mocks without having to destroy / refresh the context.
Example: You have a mock DAO:
public void MockDao implements IDao {
private Map<Long, Foo> database = Maps.newHashMap();
#Override
public Foo byId( Long id ) { return database.get( id ) );
#Override
public void save( Foo foo ) { database.put( foo.getId(), foo ); }
#After
public void reset() { database.clear(); }
}
The annotation will make sure reset() will be called after each unit test to clean up the internal state.
Using the previous answers, I've updated this to use Java 8 Streams API:
#Inject
private ApplicationContext applicationContext;
#Before
public void resetMocks() {
ConfigurableListableBeanFactory beanFactory = ((AbstractApplicationContext) applicationContext).getBeanFactory();
Stream.of(applicationContext.getBeanDefinitionNames())
.map(n -> beanFactory.getSingleton(n))
// My ConfigurableListableBeanFactory isn't compiled for 1.8 so can't use method reference. If yours is, you can say
// .map(ConfigurableListableBeanFactory::getSingleton)
.filter(b -> Mockito.mockingDetails(b).isMock())
.forEach(Mockito::reset);
}
I have a class that implements ManagedServiceFactory like this:
public class GreeterFactory implements ManagedServiceFactory {
private volatile BundleContext bundleContext =
FrameworkUtil.getBundle(GreeterFactory.class).getBundleContext();
private final Map<String, ServiceRegistration> registrations = new ConcurrentHashMap<>();
#Override
public String getName() {
return "Greeter Factory Implementation";
}
/**
* Greeter Service Factory
* #param pid this is the PID of the Configuration received.
* #param dictionary the Configuration to prepare the service.
* #throws ConfigurationException
*/
#Override
public void updated(String pid, Dictionary<String, ?> dictionary) throws ConfigurationException {
String message = (String) dictionary.get("message");
if (message == null) {
throw new ConfigurationException("message",
"Required property 'message' missing");
}
GreeterService greeter;
synchronized (registrations) {
if (registrations.containsKey(pid)) {
greeter = (GreeterService) bundleContext.getService(registrations.get(pid).getReference());
} else {
// For each new configuration, the factory register a new service with
// the given properties/configuration
greeter = new GreeterImpl();
ServiceRegistration greeterRegistration =
bundleContext.registerService(GreeterService.class.getName(),
greeter,
dictionary);
System.out.print("\nRegistering Config-PID: " + pid + "\n");
registrations.put(pid, greeterRegistration);
}
}
greeter.setMessage(message);
}
#Override
public void deleted(String pid) {
ServiceRegistration component = null;
synchronized (registrations) {
component = registrations.remove(pid);
}
// Calling services from a synchronized block can lead to deadlocks,
// so Dependency Manager must be called outside.
if(component != null) {
bundleContext.ungetService(component.getReference());
}
}
}
The factory works OK. I also have a test case to consume the services created for each configuration sent by the ConfigurationAdmin service, here is the test case:
Configuration configuration1 = configurationAdmin.createFactoryConfiguration("example.factoryservice.greeter", null);
Dictionary properties = new Properties();
properties.put("message", "Hello factory world 1!");
configuration1.update(properties);
TimeUnit.SECONDS.sleep(1);
Configuration configuration2 = configurationAdmin.createFactoryConfiguration("example.factoryservice.greeter", null);
properties = new Properties();
properties.put("message", "Hello factory world 2!");
configuration2.update(properties);
TimeUnit.SECONDS.sleep(1);
ServiceReference<GreeterService> sRef = context.getServiceReference(GreeterService.class);
GreeterService greeterService = context.getService(sRef);
assertEquals("Hello factory world 1!", greeterService.sayHello());
greeterService = context.getService(sRef);
assertEquals("Hello factory world 2!", greeterService.sayHello()); // FAILS!!
Now, I am kind of lost here and I cannot find any documentation about this part, but how do I determine in code what Greeter service to use depending on the configuration I need?
I created in code 2 Greeter configurations, the factory then registered a couple of Greeter services, each with a different configuration, how I decide in code an instance of a Greeter service with Configuration 1?
ManagedServiceFactory is pretty low level. Unless you want to implement a technology, you do not need it. In case you want to implement business logic, use one of the Component Models.
But, to answer your specific question:
You register the GreeterService with the service properties that you get from the configuration. That means that you can filter on these services.
Note, that BundleContext has a function where you can pass OSGi service filter as well. E.g.:
Collection<ServiceReference<GreeterService>> sRefs =
context.getServiceReferences(GreeterService.class,
"(message=Hello factory world 2!)");
I have 2 modules containing classes:
blog.model.ArticleDAO
blog.model.CategoryDAO
users.model.UserDAO
users.model.UserGroupDAO
All these DAOs have a dependency on the same service, but I need to inject a different instance based on the package.
I mean the module blog should have a specific instance of MyService, and the module users should have another instance of MyService.
I don't want to create 2 named services because some day I may want to use the same service for all DAOs. Or I could also want to inject another specific instance for a specific class...
Is there a way to inject a service based on the package of a class?
A way to say:
inject foo (instance of MyService) into classes that are in blog.*
inject bar (instance of MyService) into classes that are in users.*
but keeping all my classes unaware of that! Their configuration should only state "Inject an instance of MyService".
First I want to say, I find this a strange requirement. I am also wondering why your DAOs need a Service. In a normal layered design, this is the opposite (the Service uses the DAO).
However I find the challenge interesting, I tried to use a FactoryBean to create a Java Proxy class which would redirect at runtime to the correct instance of MyService depending of the caller package. Here is the code:
public class CallerPackageAwareProxyFactoryBean implements
FactoryBean<MyService>, ApplicationContextAware {
private Class<?> targetServiceType;
private ApplicationContext applicationContext;
private InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (ReflectionUtils.isEqualsMethod(method)) {
// Only consider equal when proxies are identical.
return (proxy == args[0]);
} else if (ReflectionUtils.isHashCodeMethod(method)) {
// Use hashCode of service locator proxy.
return System.identityHashCode(proxy);
} else if (ReflectionUtils.isToStringMethod(method)) {
return "Service dispatcher: " + targetServiceType.getName();
} else {
String callerPackageFirstLevel = getCallerPackageFirstLevel();
Map<String, ?> beans = applicationContext
.getBeansOfType(targetServiceType);
for (Map.Entry<String, ?> beanEntry : beans.entrySet()) {
if (beanEntry.getKey().startsWith(callerPackageFirstLevel)) {
return method.invoke(beanEntry.getValue(), args);
}
}
throw new IllegalArgumentException(
String.format(
"Could not find any valid bean to forward call for method %s.",
method.getName()));
}
}
private String getCallerPackageFirstLevel() {
Throwable t = new Throwable();
StackTraceElement[] elements = t.getStackTrace();
String callerClassName = elements[3].getClassName();
return callerClassName.split("\\.")[0];
}
};
public MyService getObject() throws Exception {
return (MyService) Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), new Class<?>[] { MyService.class },
invocationHandler);
}
public Class<?> getObjectType() {
return MyService.class;
}
public boolean isSingleton() {
return true;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void setTargetServiceType(Class<?> targetServiceType) {
this.targetServiceType = targetServiceType;
}
}
I didn't had to change anything to the Dao or Service configuration. I just had to add the creation of the FactoryBean in the Spring context:
<bean id="myService" class="stackoverflow.CallerPackageAwareProxyFactoryBean">
<property name="targetServiceType" value="a.b.c.MyService" />
</bean>
Maybe a few comments:
The caller package can only be get by creating an exception and looking at the stacktrace.
The code of the InvocationHandler is inspired from ServiceLocatorFactoryBean.
I am still wondering if there is an easier way but I think there is not.
You could replace part of the InvocationHandler to use a configuration Map (package => MyService bean name)
I would not recommend using such code in a productive environment.