Question

I'm trying to intercept the method persist and update of javax.persistence.EntityManager in a Seam 3 project.

In a previous version (Seam 2) of the micro-framework I'm trying to make, I did this using an implementation of org.hibernate.Interceptor and declaring it in the persistence.xml.

But I want something more "CDI-like" now we are in a JEE6 environment.

I want that just before entering in a EntityManager.persist call, an event @BeforeTrackablePersist is thrown. The same way, I want an event @BeforeTrackableUpdate to be thrown before entering in a EntityManager.merge call. Trackable is an interface which some of my Entitys could implement in order to be intercepted before persist or merge.

I'm using Seam 3 (3.1.0.Beta3) Extended Persistence Manager :

public class EntityManagerHandler {
    @SuppressWarnings("unused")
    @ExtensionManaged
    @Produces
    @PersistenceUnit
    private EntityManagerFactory entityManagerFactory;
}

So I've made a javax.enterprise.inject.spi.Extension, and tryied many ways to do that :

public class TrackableExtension implements Extension {

    @Inject @BeforeTrackablePersisted
    private Event<Trackable> beforeTrackablePersistedEvent;

    @Inject @BeforeTrackableMerged
    private Event<Trackable> beforeTrackableMergedEvent;

    @SuppressWarnings("unchecked")
    public void processEntityManagerTarget(@Observes final ProcessInjectionTarget<EntityManager> event) {
        final InjectionTarget<EntityManager> injectionTarget = event.getInjectionTarget();
        final InjectionTarget<EntityManager> injectionTargetProxy = (InjectionTarget<EntityManager>) Proxy.newProxyInstance(event.getClass().getClassLoader(), new Class[] {InjectionTarget.class}, new InvocationHandler() {
            @Override
            public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                if ("produce".equals(method.getName())) {
                    final CreationalContext<EntityManager> ctx = (CreationalContext<EntityManager>) args[0];
                    final EntityManager entityManager = decorateEntityManager(injectionTarget, ctx);
                    return entityManager;
                } else {
                    return method.invoke(injectionTarget, args);
                }
            }
        });
        event.setInjectionTarget(injectionTargetProxy);
    }

    public void processEntityManagerType(@Observes final ProcessAnnotatedType<EntityManager> event) {
        final AnnotatedType<EntityManager> type = event.getAnnotatedType();
        final AnnotatedTypeBuilder<EntityManager> builder = new AnnotatedTypeBuilder<EntityManager>().readFromType(type);

        for (final AnnotatedMethod<? super EntityManager> method : type.getMethods()) {
            final String name = method.getJavaMember().getName();
            if (StringUtils.equals(name, "persist") || StringUtils.equals(name, "merge")) {
                builder.addToMethod(method, TrackableInterceptorBindingLiteral.INSTANCE);
            }
        }

        event.setAnnotatedType(builder.create());
    }

    public void processEntityManagerBean(@Observes final ProcessBean<EntityManager> event) {
        final AnnotatedType<EntityManager> annotatedType = (AnnotatedType<EntityManager>)event.getAnnotated();
//      not even called
    }

    public void processEntityManager(@Observes final ProcessProducer<?, EntityManager> processProducer) {
        processProducer.setProducer(decorate(processProducer.getProducer()));
    }

    private Producer<EntityManager> decorate(final Producer<EntityManager> producer) {
        return new Producer<EntityManager>() {
            @Override
            public EntityManager produce(final CreationalContext<EntityManager> ctx) {
                return decorateEntityManager(producer, ctx);
            }
            @Override
            public Set<InjectionPoint> getInjectionPoints() {
                return producer.getInjectionPoints();
            }
            @Override
            public void dispose(final EntityManager instance) {
                producer.dispose(instance);
            }
        };
    }

    private EntityManager decorateEntityManager(final Producer<EntityManager> producer, final CreationalContext<EntityManager> ctx) {
        final EntityManager entityManager = producer.produce(ctx);
        return (EntityManager) Proxy.newProxyInstance(entityManager.getClass().getClassLoader(), new Class[] {EntityManager.class}, new InvocationHandler() {
            @Override
            public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                final String methodName = method.getName();
                if (StringUtils.equals(methodName, "persist")) {
                    fireEventIfTrackable(beforeTrackablePersistedEvent, args[0]);
                } else if (StringUtils.equals(methodName, "merge")) {
                    fireEventIfTrackable(beforeTrackableMergedEvent, args[0]);
                }
                return method.invoke(entityManager, args);
            }

            private void fireEventIfTrackable(final Event<Trackable> event, final Object entity) {
                if (entity instanceof Trackable) {
                    event.fire(Reflections.<Trackable>cast(entity));
                }
            }
        });
    }
}

In all those observer methods, only the second one (processEntityManagerType(@Observes ProcessAnnotatedType<EntityManager>)) is called ! And even with that binding addition to methods persist and merge, my Interceptor is never called (I've of course enabled it with the correct lines in beans.xml, and enabled my extension with the services/javax.enterprise.inject.spi.Extension file).

Something I've thought simple with CDI seems to be actually really hard at last... or perhaps Seam 3 does something which prevent this code from executing correctly...

Does someone know how to handle that ?

Was it helpful?

Solution 2

As finally my little patch for Seam Persistence was applied in SEAMPERSIST-75, it will be possible in theory to do that by extending org.jboss.seam.persistence.HibernatePersistenceProvider and override the method proxyEntityManager(EntityManager).

OTHER TIPS

I think you're making this a little harder than what it needs to be. Firstly though, JPA and CDI integration isn't very good in Java EE 6, we're very much hoping that changes in Java EE 7 and JPA 2.1.

What you'll want to do is create your own producer for the EntityManager that will delegate to an actual instance of an EntityManager, but also fire your own events when you call the methods you're interested in. Take a look at the Seam Persistence source to see one way this can be done.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top