Reputation: 459
I am new to AEM OSGI , any help would be appreciated I have a class which contains @Activate annotated activate method inside which i am resolving and building up resources
@Component
@Service(MyTest.class)
public class MyTest {
private static final Logger LOG = LoggerFactory.getLogger(MyTest.class);
...
...
@Reference
private ResourceResolverFactory resolverFactory;
@Activate
protected void activate() {
final ResourceResolver resolver;
try {
resolver = resolverFactory.getAdministrativeResourceResolver(null);
} catch (LoginException e) {
LOG.error("error resolving resource resolver", e);
return;
}
I have a servlet that invokes this class and on the servlet i am using
@Reference
MyTest test;
@Override
protected void doPost
....
Here is the error i am getting
java.lang.RuntimeException: Unable to invoke method 'activate' for class com.demo.MyTest
java.lang.RuntimeException: Unable to invoke method 'activate' for class com.demo.MyTest at org.apache.sling.testing.mock.osgi.OsgiServiceUtil.invokeMethod(OsgiServiceUtil.java:263)
at org.apache.sling.testing.mock.osgi.OsgiServiceUtil.activateDeactivate(OsgiServiceUtil.java:101)
at org.apache.sling.testing.mock.osgi.MockOsgi.activate(MockOsgi.java:211)
at org.apache.sling.testing.mock.osgi.MockOsgi.activate(MockOsgi.java:222)
at org.apache.sling.testing.mock.osgi.context.OsgiContextImpl.registerInjectActivateService(OsgiContextImpl.java:155)
at org.apache.sling.testing.mock.osgi.context.OsgiContextImpl.registerInjectActivateService(OsgiContextImpl.java:142)
at com.Demo.MyDemoTest(MyDemoTest.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.NullPointerException
at com.Demo.MyTest.activate(MyTest.java:75)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.sling.testing.mock.osgi.OsgiServiceUtil.invokeMethod(OsgiServiceUtil.java:254)
... 33 more
Please help me understand where i am making the mistake Also if i move the resolver factory definition to a public method inside that same class its working perfectly
Upvotes: 0
Views: 2222
Reputation: 21500
Although not explicitly mentioned in the question the provided stacktrace reveals that the service is "run" within a unit test.
Furthermore, the stack trace reveals, that the OsgiContext
is used, which does not provide an implementation of the ResourceResolverFactory
.
Since no ResourceResolverFactory
is registered within the mock OSGi context, the @Reference
can not be injected upon service registration and activation. When the ResourceResolverFactory
is then called in the activate method the reference is null
and therefore the NullPointerException
is thrown.
Therefore, I would advise to use the excellent AemContext
which is provided by the wcm.io aem-mock framework or at least the SlingContext
provided by sling-mocks.
The unit test would look like this:
public class MyUnitTest {
@Rule
public AemContext context;
@Test
public void someTest() {
MyTest service = context.registerInjectActivateService(new MyTest());
[... additional test code ...]
}
}
Since the AemContext
already has a functional ResourceResolverFactory
(mock) registered, the unit test code does not have to create a mock and register it. When the registerInjectActivateService()
method is called a new instance of the MyTest
class is instantiated and the referenced ResourceResolverFactory
is injected.
Please do not create service-wide ResourceResolvers
. This is a bad practice. ResourceResolver
should be short-lived. That means that they are only used for a few "operations" (like reading a resource) and then discarded.
The best way to do this is to use the try-with-resource
statement like this:
public class MyTest {
private static final SERVICE_NAME = "MyTestService";
private static final Map<String, Object> authenticationInfo = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SERVICE_NAME);
@Reference
private ResourceResolverFactory resourceResolverFactory;
public void someMethod() {
try (ResourceResolver resolver = getResourceResolver()) {
[... use resolver to do stuff in JCR ...]
}
}
private ResourceResolver getResourceResolver() {
try {
return resourceResolverFactory.getServiceResourceResolver(authenticationInfo);
} catch (LoginException cause) {
throw new IllegalStateException("Unable to obtain ResourceResolver!", cause)
}
}
}
I chose to create a separate method to create the ResourceResolver
to avoid cluttering someMethod()
with exception handling. But that is obviously something that can be changed.
Since administrative ResourceResolver
are deprecated I also chose to use a service ResourceResolver
. To use those you need to create a service user mapping. You can find out more about this in the documentation.
Upvotes: 3
Reputation: 2021
Please note that creating the resolver inside of Acivate method is anti-pattern. Your service might be called by multiple threads in parallel, and that these calls can be processed in parallel. When we do write or read a resource then JCR session apply internal lock, which prevents multiple sessions to work in parallel on the very same session.
The best way to avoid this issue is to create resolver inside a method which you override.
@Override
performSomeOperation(){
final ResourceResolver resolver;
try {
resolver = resolverFactory.getAdministrativeResourceResolver(null);
} catch (LoginException e) {
LOG.error("error resolving resource resolver", e);
return;
}
}
Upvotes: 0