2

I'm trying to inject an EntityManager into a jersey resource and into dependent repository services. The idea is to start and commit a transaction at the boundaries of the REST request, so the same entity manager must be used. To achieve this, I've created a custom HK2 scope @TransactionScope, using an operation.

The application is running as Java SE application, the jersey resources being only one part of it. Other services run in the background, triggered through schedulers or JMS, and they also use the same scope. Java 11, current versions of jersey, HK2, Eclipselink, etc.

The scope is defined @Proxiable:

    @Scope
    @Proxiable(proxyForSameScope = false)
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface TransactionScope { }

The creation of the EntityManager is registered in the root servicelocator using the scope:

    bindFactory(EMFactory.class)
            .to(EntityManager.class)
            .in(TransactionScope.class);

Repository services that use the EntityManager are @Singleton. These provide the CRUD operations around database entities. Thanks to the proxied injection, they get the same entity manager while in the same scope.

The idea is:

  1. At the transaction boundary, start the HK2 operation. For a jersey REST request, this is implemented through a ContainerRequestFilter; for a background service, it is the scheduler that runs a job, or the message listener that receives a JMS message.
  2. The jersey resource/scheduled job/message processor begins the database transaction entityManager.getTransaction().begin(), then executes is specific logic.
  3. Repository services are injected and provide CRUD operations for the database entities.
  4. The transaction is committed.
  5. The HK2 operation is closed.

All of this works fine when injecting the entity manager into the background services. But as soon as I attempt to inject it into a jersey resource, an IncompatibleClassChangeError is thrown.

ERROR c.s.s.e.RuntimeExceptionMapper - Caught A MultiException has 2 exceptions.  They are:
1. java.lang.IncompatibleClassChangeError: class javax.persistence.EntityManager_$$_jvsted_0 has interface javax.persistence.EntityManager as super class
2. java.lang.IllegalArgumentException: While attempting to create a Proxy for javax.persistence.EntityManager in scope com.skalio.rsem.TransactionScope an error occured while creating the proxy
 at org.jvnet.hk2.internal.ProxyUtilities.generateProxy(ProxyUtilities.java:211)

Researching this error, it is often recommended to check for duplicate and conflicting libraries on the classpath. I believe this is not the case here (at least I can't find anything).

I cannot use @RequestScoped, as it is only available to the jersey resources, not the background services.

Any suggestions what I can do?

Update

I've noticed that the following works:

  • @TransactionScope is marked @Unproxiable
  • Injection of the EntityManager occurs via a javax.inject.Provider

This is possible way forward for me, even though I find it not as clean. A developer could forget that Provider-injection must be used.

Update regarding dependencies

I've looked more into the dependencies. Slight surprise for me: EclipseLink does not use javax.persistence:javax.persistence-api but instead org.eclipse.persistence:jakarta.persistence. Regardless, that is still the only place on the classpath containing javax.persistence packages.

[INFO] +- org.eclipse.persistence:eclipselink:jar:2.7.7:compile
[INFO] |  +- org.eclipse.persistence:jakarta.persistence:jar:2.2.3:compile
[INFO] |  \- org.eclipse.persistence:commonj.sdo:jar:2.1.1:compile
[INFO] +- org.eclipse.persistence:org.eclipse.persistence.jpa.modelgen.processor:jar:2.7.7:provided
[INFO] |  +- org.eclipse.persistence:org.eclipse.persistence.core:jar:2.7.7:provided
[INFO] |  |  \- org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.7.7:provided
[INFO] |  \- org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.7.7:provided
[INFO] |     +- org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.7.7:provided
[INFO] |     \- org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.7.7:provided

Update regarding service locators

Since only part of the code lives in jersey and the rest in the "background", I register as much as possible in the "root" ServiceLocator. To give jersey access, I bridge the locator from jersey into the root locator.

    // configures the jersey container
    public class ApplicationConfig extends ResourceConfig {

        public ApplicationConfig(ServiceLocator parentLocator) {
    
            // These services need to be registered into jersey's locator
            register(new JerseyContainerBinder());

            // bridge services into jersey
            register(new ServiceLocatorBridge(parentLocator));
        }
    }

The JerseyContainerBinder is currently empty, I can use it to extend or override root-locator-bindings. The ServiceLocatorBridge fetches jersey's locator and connect it: ExtrasUtilities.bridgeServiceLocator(jerseyLocator, sourceLocator);

So this is where there is a difference between injecting something in the background compared to in a jersey resource: it has to go through the bridge first. Not sure if that matters. Ah, if the jersey folks would only allow reusing an existing locator...

Hank
  • 4,597
  • 5
  • 42
  • 84
  • The error would suggest that the `EntityManager` interface version for which the proxy is created does not match the version expected by the client code (the reason `@Unproxiable` works might be that, when trying to create a proxy, hk2 picks up the wrong class definition). Are you absolutely sure you're not pulling in some version of JPA API/annotations jar which is incompatible with the EclipseLink's runtime version? – crizzis Aug 11 '20 at 14:00
  • If you're using a JPA API jar, the simplest way to verify it would be to replace it temporarily with a provided-scope EclipseLink implementation, with the version matching the runtime EclipseLink's version exactly – crizzis Aug 11 '20 at 14:03
  • Thanks! See update above re dependencies. Could you elaborate re your second commend please? Why does `@Proxiable` work for background services, but not jersey-injected services? – Hank Aug 12 '20 at 07:57
  • Is it EntityManager itself (the interface) that you are injecting? Can you show one example of an injection point. If it IS EntityManager you are injecting, which is an interface, this is very strange as it would then be the java reflection library creating the proxy. (If it is a class being injected we had to use a different library). Also... I'm excited to hear someone is using operations lol! I wrote them, and we use them a lot ourselves but I wasn't sure anyone else was. I think they are SO useful! – jwells131313 Aug 12 '20 at 14:21
  • Yes, I inject `javax.persistence.EntityManager`, the interface. It is created via the `EMFactory` (see above, also see note regarding service locator). I'll try to simplify my test project so I can publish it for review. Operations are cool. Couldn't believe how little code was necessary to create a scope-boundary. – Hank Aug 12 '20 at 14:36
  • I know, it's crazy right? I actually brought them up in a standards meeting for CDI but... no-one bit. It's possible I explained them badly lol – jwells131313 Aug 12 '20 at 15:12

0 Answers0