/*
* Copyright 2011 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.persistence.marshalling.util;
import static org.drools.persistence.marshalling.util.MarshallingTestUtil.PROCESS_INSTANCE_INFO_CLASS_NAME;
import static org.drools.persistence.marshalling.util.MarshallingTestUtil.getProcessInstanceInfoByteArray;
import static org.drools.persistence.marshalling.util.MarshallingTestUtil.getWorkItemByteArray;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.drools.persistence.info.SessionInfo;
import org.drools.persistence.info.WorkItemInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This object serves as a proxy for the EntityManagerFactory and the EntityManager instances.
* </p>
* It ensures that when objects that contain marshalled data are persisted, a new MarshalledData
* object is also persisted with the most recent snapshot of the marshalled data. A new MarshalledData
* is only made when the marshalled data has changed.
*/
public class EntityManagerFactoryProxy implements InvocationHandler {
private static Logger logger = LoggerFactory.getLogger(EntityManagerFactoryProxy.class);
private EntityManagerFactory emf;
private EntityManager em;
// ensure that Hibernate does not proxy the Map implementation objects
protected transient static ThreadLocal<Map<SessionInfo, byte []>> managedSessionInfoDataMap;
protected transient static ThreadLocal<Map<WorkItemInfo, byte []>> managedWorkItemInfoDataMap;
protected transient static ThreadLocal<Map<Object, byte []>> managedProcessInstanceInfoDataMap;
protected transient static ThreadLocal<Map<Long, byte[]>> sessionMarshalledDataMap;
protected transient static ThreadLocal<Map<Long, byte[]>> workItemMarshalledDataMap;
protected transient static ThreadLocal<Map<Long, byte[]>> processInstanceInfoMarshalledDataMap;
/**
* This method creates a proxy for either a {@link EntityManagerFactory} or a {@link EntityManager} instance.
* @param obj The original instance for which a proxy will be made.
* @return Object a proxy instance of the given object.
*/
public static Object newInstance( Object obj ) {
if( obj instanceof EntityManagerFactory || obj instanceof EntityManager ) {
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
getAllInterfaces(obj),
new EntityManagerFactoryProxy(obj));
}
else {
throw new UnsupportedOperationException("This proxy is only for "
+ EntityManagerFactory.class.getSimpleName() + " and " + EntityManager.class.getSimpleName() + " instances." );
}
}
/**
* This method is used in the {@link #newInstance(Object)} method to retrieve all applicable interfaces
* that the proxy object must conform to.
* @param obj The object that will be proxied.
* @return Class<?> [] an array of all applicable interfaces.
*/
protected static Class<?> [] getAllInterfaces( Object obj ) {
Class<?> [] interfaces = new Class [0];
Class<?> superClass = obj.getClass();
while( superClass != null ) {
Class<?> [] addThese = superClass.getInterfaces();
if( addThese.length > 0 ) {
Class<?> [] moreinterfaces = new Class [interfaces.length + addThese.length];
System.arraycopy(interfaces, 0, moreinterfaces, 0, interfaces.length);
System.arraycopy(addThese, 0, moreinterfaces, interfaces.length, addThese.length);
interfaces = moreinterfaces;
}
superClass = superClass.getSuperclass();
}
return interfaces;
}
/**
* This is the constructor that follows the InvocationHandler design pattern, so to speak. <br/>
* It saves the @{link {@link EntityManager} or {@link EntityManagerFactory} for use later.
* @param obj The object being proxied.
*/
private EntityManagerFactoryProxy(Object obj ) {
if( obj instanceof EntityManagerFactory ) {
this.emf = (EntityManagerFactory) obj;
}
else if( obj instanceof EntityManager ) {
this.em = (EntityManager) obj;
}
else {
throw new UnsupportedOperationException("This proxy is only for "
+ EntityManagerFactory.class.getSimpleName() + " and " + EntityManager.class.getSimpleName() + " instances." );
}
}
private synchronized void lazyInitializeStateMaps(Object [] args) {
if( args == null || args.length == 0 ) {
return;
}
if( args[0] instanceof SessionInfo ) {
if( managedSessionInfoDataMap == null ) {
managedSessionInfoDataMap = new ThreadLocal<Map<SessionInfo, byte[]>>();
sessionMarshalledDataMap = new ThreadLocal<Map<Long, byte[]>>();
}
if( managedSessionInfoDataMap.get() == null ) {
managedSessionInfoDataMap.set(new HashMap<SessionInfo, byte[]>());
sessionMarshalledDataMap.set(new HashMap<Long, byte[]>());
}
}
else if( args[0] instanceof WorkItemInfo ) {
if( managedWorkItemInfoDataMap == null ) {
managedWorkItemInfoDataMap = new ThreadLocal<Map<WorkItemInfo, byte[]>>();
workItemMarshalledDataMap = new ThreadLocal<Map<Long, byte[]>>();
}
if( managedWorkItemInfoDataMap.get() == null ) {
managedWorkItemInfoDataMap.set(new HashMap<WorkItemInfo, byte[]>());
workItemMarshalledDataMap.set(new HashMap<Long, byte[]>());
}
}
else if( PROCESS_INSTANCE_INFO_CLASS_NAME.equals(args[0].getClass().getName()) ) {
if( managedProcessInstanceInfoDataMap == null ) {
managedProcessInstanceInfoDataMap = new ThreadLocal<Map<Object, byte[]>>();
processInstanceInfoMarshalledDataMap = new ThreadLocal<Map<Long, byte[]>>();
}
if( managedProcessInstanceInfoDataMap.get() == null ) {
managedProcessInstanceInfoDataMap.set(new HashMap<Object, byte[]>());
processInstanceInfoMarshalledDataMap.set(new HashMap<Long, byte[]>());
}
}
}
/**
* {@inheritDoc}
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
logger.trace(methodName);
lazyInitializeStateMaps(args);
if( "createEntityManager".equals(methodName) ) {
return createEntityManager(methodName, args);
}
else if( "persist".equals(methodName) && args.length == 1 ) {
em.persist(args[0]);
String testMethodName = MarshallingTestUtil.getTestMethodName();
if( testMethodName != null ) {
persist(testMethodName, args);
}
return result;
}
else if( "merge".equals(methodName) && args.length == 1 ) {
result = em.merge(args[0]);
String testMethodName = MarshallingTestUtil.getTestMethodName();
if( testMethodName != null ) {
merge(testMethodName, result);
}
return result;
}
else if( "find".equals(methodName) && args.length == 2) {
result = em.find((Class<?>) args[0], args[1]);
find(result);
}
else {
Class<?> methodClass = method.getDeclaringClass();
if( methodClass.equals(EntityManagerFactory.class) ) {
result = invoke(method, emf, args);
}
else if( methodClass.equals(EntityManager.class) ) {
result = invoke(method, em, args);
}
else {
RuntimeException re = new RuntimeException("Unexpected class " + methodClass + " for method " + methodName );
re.fillInStackTrace();
throw re;
}
}
return result;
}
private Object invoke( Method method, Object object, Object[] args) throws Throwable {
Object result = null;
try {
result = method.invoke(object, args);
} catch( InvocationTargetException ite ) {
logger.warn(method.getName() + " threw " + ite.getClass().getSimpleName() + ": " + ite.getMessage());
throw ite;
}
return result;
}
/**
* This method creates a proxy for an EntityManager generated by the real EntityManagerFactory.
* @param methodName The name of the test method in which this method is called.
* @param args The arguments to the EntityManagerFactory.createEntityManager(...) method
* @return Object a proxy of a EntityManager instance.
*/
private Object createEntityManager(String methodName, Object [] args) {
EntityManager realEm = null;
if( args == null ) {
realEm = (EntityManager) emf.createEntityManager();
}
else if( args[0] instanceof Map<?,?>){
realEm = (EntityManager) emf.createEntityManager((Map<?,?>) args[0]);
}
else {
String message = "Method " + methodName + " with args (";
for( int i = 0; i < args.length; ++i ) {
message += args[i].getClass() + ", ";
}
message = message.substring(0, message.lastIndexOf(",")) + ") not supported!";
throw new UnsupportedOperationException(message);
}
return newInstance(realEm);
}
/**
* This method stores a MarshalledData object for all objects that contain marshalled data.
* @param methodName The name of the test method in which this happens.
* @param args The arguments to EntityManager.persist(...)
*/
private void persist(String methodName, Object[] args) {
MarshalledData marshalledData = null;
if( args[0] instanceof SessionInfo ) {
SessionInfo sessionInfo = (SessionInfo) args[0];
byte [] byteArray = sessionInfo.getData();
managedSessionInfoDataMap.get().put(sessionInfo, byteArray != null ? byteArray.clone() : null );
if( byteArray != null ) {
marshalledData = new MarshalledData(sessionInfo);
em.persist(marshalledData);
logger.trace("-.-: " + marshalledData);
}
}
else if( args[0] instanceof WorkItemInfo ) {
WorkItemInfo workItemInfo = (WorkItemInfo) args[0];
byte [] byteArray = getWorkItemByteArray(workItemInfo);
managedWorkItemInfoDataMap.get().put(workItemInfo, byteArray != null ? byteArray.clone() : null );
if( byteArray != null ) {
marshalledData = new MarshalledData(workItemInfo);
em.persist(marshalledData);
logger.trace("-.-: " + marshalledData);
}
}
else if( PROCESS_INSTANCE_INFO_CLASS_NAME.equals(args[0].getClass().getName()) ) {
byte [] byteArray = MarshallingTestUtil.getProcessInstanceInfoByteArray(args[0]);
managedProcessInstanceInfoDataMap.get().put(args[0], byteArray != null ? byteArray.clone() : null);
Long id = MarshallingTestUtil.getProcessInstanceInfoId(args[0]);
processInstanceInfoMarshalledDataMap.get().put(id, byteArray);
if( byteArray != null ) {
marshalledData = new MarshalledData(args[0]);
em.persist(marshalledData);
logger.trace("-.-: " + marshalledData);
}
}
}
private void merge(String testMethodName, Object updatedObject) {
if( updatedObject instanceof SessionInfo ) {
HashMap<SessionInfo, byte[]> updatedObjectsMap = new HashMap<SessionInfo, byte[]>();
updateSessionInfoMarshalledData((SessionInfo) updatedObject, testMethodName, em, updatedObjectsMap);
if( ! updatedObjectsMap.isEmpty() ) {
managedSessionInfoDataMap.get().put((SessionInfo) updatedObject, updatedObjectsMap.get(updatedObject));
}
}
if( updatedObject instanceof WorkItemInfo ) {
HashMap<WorkItemInfo, byte[]> updatedObjectsMap = new HashMap<WorkItemInfo, byte[]>();
updateWorkItemInfoMarshalledData((WorkItemInfo) updatedObject, testMethodName, em, updatedObjectsMap);
if( ! updatedObjectsMap.isEmpty() ) {
managedWorkItemInfoDataMap.get().put((WorkItemInfo) updatedObject, updatedObjectsMap.get(updatedObject));
}
}
if( PROCESS_INSTANCE_INFO_CLASS_NAME.equals(updatedObject.getClass().getName()) ) {
HashMap<Object, byte[]> updatedObjectsMap = new HashMap<Object, byte[]>();
updateProcessInstanceInfoMarshalledData(updatedObject, testMethodName, em, updatedObjectsMap);
if( ! updatedObjectsMap.isEmpty() ) {
managedProcessInstanceInfoDataMap.get().put(updatedObject, updatedObjectsMap.get(updatedObject));
}
}
}
/**
* Add the object to the internal map of managed objects.
* @param result The objec
*/
private void find(Object result) {
if( result == null ) {
return;
}
if( result instanceof SessionInfo ) {
byte [] data = managedSessionInfoDataMap.get().get(result);
if( data == null ) {
byte [] byteArray = ((SessionInfo) result).getData();
managedSessionInfoDataMap.get().put((SessionInfo) result, byteArray);
}
}
else if( result instanceof WorkItemInfo ) {
byte [] data = managedWorkItemInfoDataMap.get().get(result);
if( data == null ) {
byte [] byteArray = getWorkItemByteArray((WorkItemInfo) result);
managedWorkItemInfoDataMap.get().put((WorkItemInfo) result, byteArray);
}
}
else if( PROCESS_INSTANCE_INFO_CLASS_NAME.equals(result.getClass().getName())) {
byte [] data = managedProcessInstanceInfoDataMap.get().get(result);
if( data == null ) {
byte [] byteArray = MarshallingTestUtil.getProcessInstanceInfoByteArray(result);
managedProcessInstanceInfoDataMap.get().put(result, byteArray);
}
}
}
/**
* This is the method that checks whether or not (managed) objects that (can) contain
* marshalled data, have marshalled data fields that have been updated. If so, this method
* (and the methods it calls) create a new MarshalledData object to store a snapshot of
* the new marshalled data and persist it.
* @param testMethodName The name of the test method in which the marshalled data has been created.
* @param em An EntityManager in order to persist the MarshalledData object.
*/
protected static void updateManagedObjects(String testMethodName, EntityManager em) {
// Update the marshalled data belonging to managed SessionInfo objects
if( managedSessionInfoDataMap != null ) {
HashMap<SessionInfo, byte []> updatedObjectsMap = new HashMap<SessionInfo, byte[]>();
for( SessionInfo sessionInfo : managedSessionInfoDataMap.get().keySet()) {
updateSessionInfoMarshalledData(sessionInfo, testMethodName, em, updatedObjectsMap);
}
for( SessionInfo sessionInfo : updatedObjectsMap.keySet() ) {
managedSessionInfoDataMap.get().put(sessionInfo, updatedObjectsMap.get(sessionInfo));
}
}
// Update the marshalled data belonging to managed WorkItemInfo objects
if( managedWorkItemInfoDataMap != null ) {
HashMap<WorkItemInfo, byte []> updatedObjectsMap = new HashMap<WorkItemInfo, byte[]>();
for( WorkItemInfo workItemInfo : managedWorkItemInfoDataMap.get().keySet() ) {
updateWorkItemInfoMarshalledData(workItemInfo, testMethodName, em, updatedObjectsMap);
}
for( WorkItemInfo workItemInfo : updatedObjectsMap.keySet() ) {
managedWorkItemInfoDataMap.get().put(workItemInfo, updatedObjectsMap.get(workItemInfo));
}
}
// Update the marshalled data belonging to managed ProcessInstanceInfo objects
if( managedProcessInstanceInfoDataMap != null ) {
HashMap<Object, byte []> updatedObjectsMap = new HashMap<Object, byte[]>();
for( Object processInstanceInfo : managedProcessInstanceInfoDataMap.get().keySet() ) {
updateProcessInstanceInfoMarshalledData(processInstanceInfo, testMethodName, em, updatedObjectsMap);
}
for( Object processInstanceInfoObject : updatedObjectsMap.keySet() ) {
managedProcessInstanceInfoDataMap.get().put(processInstanceInfoObject,
updatedObjectsMap.get(processInstanceInfoObject));
}
}
}
private static void updateSessionInfoMarshalledData(SessionInfo sessionInfo, String testMethodName,
EntityManager em, HashMap<SessionInfo, byte []> updateManagedSessionInfoMap) {
byte [] origMarshalledBytes = managedSessionInfoDataMap.get().get(sessionInfo);
if( Arrays.equals(origMarshalledBytes, sessionInfo.getData()) ) {
// If the marshalled data in this object has NOT been changed, skip this object.
return;
}
updateManagedSessionInfoMap.put(sessionInfo, sessionInfo.getData());
// Retrieve the most recent marshalled data for this object that was saved in this test method
byte [] thisMarshalledData = sessionMarshalledDataMap.get().get(testMethodName);
// ? If there has been no data persisted for this object for this test method (yet),
// ? Or if the most recently persisted data is NOT the same as what's now been persisted,
// -> then it's "new" marshalled data, so save it in a MarshalledData object.
if( thisMarshalledData == null || ! Arrays.equals(thisMarshalledData, sessionInfo.getData()) ) {
MarshalledData marshalledData = new MarshalledData(sessionInfo);
em.persist(marshalledData);
sessionMarshalledDataMap.get().put(sessionInfo.getId(), marshalledData.byteArray);
logger.trace("-!-: " + marshalledData);
}
}
private static void updateWorkItemInfoMarshalledData(WorkItemInfo workItemInfo, String testMethodName,
EntityManager em, HashMap<WorkItemInfo, byte []> updateManagedWorkItemInfoMap) {
byte [] origMarshalledBytes = managedWorkItemInfoDataMap.get().get(workItemInfo);
byte [] workItemByteArray = getWorkItemByteArray(workItemInfo);
if( Arrays.equals(origMarshalledBytes, workItemByteArray) ) {
// If the marshalled data in this object has NOT been changed, skip this object.
return;
}
updateManagedWorkItemInfoMap.put(workItemInfo, workItemByteArray);
// Retrieve the most recent marshalled data for this object that was saved in this test method
byte [] thisMarshalledData = workItemMarshalledDataMap.get().get(testMethodName);
// ? If there has been no data persisted for this object for this test method (yet),
// ? Or if the most recently persisted data is NOT the same as what's now been persisted,
// -> then it's "new" marshalled data, so save it in a MarshalledData object.
if( thisMarshalledData == null || ! Arrays.equals(thisMarshalledData, workItemByteArray) ) {
MarshalledData marshalledData = new MarshalledData(workItemInfo);
em.persist(marshalledData);
workItemMarshalledDataMap.get().put(workItemInfo.getId(), marshalledData.byteArray);
logger.trace("-!-: " + marshalledData);
}
}
private static void updateProcessInstanceInfoMarshalledData(Object processInstanceInfo, String testMethodName,
EntityManager em, HashMap<Object, byte []> updateManagedProcessInfoMap) {
byte [] origMarshalledBytes = managedProcessInstanceInfoDataMap.get().get(processInstanceInfo);
byte [] currMarshalledBytes = getProcessInstanceInfoByteArray(processInstanceInfo);
if( Arrays.equals(origMarshalledBytes, currMarshalledBytes) ) {
// If the marshalled data in this object has NOT been changed, skip this object.
return;
}
updateManagedProcessInfoMap.put(processInstanceInfo, currMarshalledBytes);
// Retrieve the most recent marshalled data for this object that was saved in this test method
Long id = MarshallingTestUtil.getProcessInstanceInfoId(processInstanceInfo);
byte [] thisMarshalledData = processInstanceInfoMarshalledDataMap.get().get(id);
// ? If there has been no data persisted for this object for this test method (yet),
// ? Or if the most recently persisted data is NOT the same as what's now been persisted,
// -> then it's "new" marshalled data, so save it in a MarshalledData object.
if( thisMarshalledData == null || ! Arrays.equals(thisMarshalledData, currMarshalledBytes) ) {
MarshalledData marshalledData = new MarshalledData(processInstanceInfo);
em.persist(marshalledData);
processInstanceInfoMarshalledDataMap.get().put(id, marshalledData.byteArray);
logger.trace("-!-: " + marshalledData);
}
}
}