package com.maxifier.guice.jpa;
import com.google.common.reflect.Reflection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Provides @DB context-sensitive {@code EntityManager} and handles it's methods calls.
* <p>Create {@code EntityManager} proxy which delegates calls to actual EntityManages from current {@link UnitOfWork}.</p>
* <p>Use {@code UnitOfWork} or {@code @DB} to initialize database context.</p>
*
* @author Konstantin Lyamshin (2015-11-15 21:12)
*/
@Singleton
public class DBEntityManagerProvider implements Provider<EntityManager>, InvocationHandler {
private static final Method CLOSE = locateMethodIfExists("close");
private static final Method IS_OPEN = locateMethodIfExists("isOpen");
private static final Method GET_ENTITY_MANAGER_FACTORY = locateMethodIfExists("getEntityManagerFactory");
private static final Method GET_METAMODEL = locateMethodIfExists("getMetamodel");
private static final Logger logger = LoggerFactory.getLogger(DBEntityManagerProvider.class);
private final EntityManagerFactory entityManagerFactory;
private final EntityManager proxy;
@Inject
public DBEntityManagerProvider(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
this.proxy = Reflection.newProxy(EntityManager.class, this);
}
@Override
public EntityManager get() {
return proxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args); // Don't proxy Object's methods
}
if (method.equals(CLOSE)) {
logger.warn("Try to manually close EntityManager", new UnsupportedOperationException());
return null; // ignore
}
UnitOfWork context = UnitOfWork.get();
if (context == null) {
if (method.equals(IS_OPEN)) {
return false; // no db context available
}
if (method.equals(GET_ENTITY_MANAGER_FACTORY)) {
return entityManagerFactory; // EntityManagerFactory is always available
}
if (method.equals(GET_METAMODEL)) {
return entityManagerFactory.getMetamodel(); // Meta model is always available
}
throw new IllegalStateException("No active DB context found use @DB or UnitOfWork to set it up");
}
EntityManager entityManager = context.getConnection(entityManagerFactory);
// delegate to regular implementation
return method.invoke(entityManager, args);
}
@Nullable
private static Method locateMethodIfExists(String name, Class<?>... parameterTypes) {
try {
return EntityManager.class.getMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
return null;
}
}
@Override
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
public boolean equals(Object o) {
return o == this || o == proxy;
}
@Override
public int hashCode() {
return System.identityHashCode(proxy);
}
@Override
public String toString() {
UnitOfWork context = UnitOfWork.get();
if (context != null) {
return String.format("EntityManagerProxy{%s}", context);
}
return "EntityManagerProxy{}";
}
}