codeexplorer
codeexplorer

Reputation: 459

Sling ResourceResolverFactory inside @Activate throws RunTimeException

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

Answers (2)

Jens
Jens

Reputation: 21500

Reason for the NullPointerException

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 NullPointerExceptionis thrown.

Proposed Solution

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.

Additional Note

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

Pakira
Pakira

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

Related Questions