/*
* Copyright 2010 Impetus Infotech.
*
* 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 com.impetus.kundera.proxy.cglib;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.persistence.PersistenceException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.NoOp;
import com.impetus.kundera.LazyInitializationException;
import com.impetus.kundera.ejb.EntityManagerImpl;
import com.impetus.kundera.proxy.KunderaProxy;
import com.impetus.kundera.proxy.LazyInitializer;
/**
* A <tt>LazyInitializer</tt> implemented using the CGLIB bytecode generation
* library.
*/
public final class CglibLazyInitializer implements LazyInitializer,
InvocationHandler {
/** The Constant log. */
private static final Log log = LogFactory
.getLog(CglibLazyInitializer.class);
/** The entity name. */
private String entityName;
/** The id. */
private String id;
/** The target. */
private Object target;
/** The initialized. */
private boolean initialized;
/** The unwrap. */
private boolean unwrap;
/** The persistent class. */
protected Class<?> persistentClass;
/** The get identifier method. */
protected Method getIdentifierMethod;
/** The set identifier method. */
protected Method setIdentifierMethod;
/** The interfaces. */
private Class<?>[] interfaces;
/** The constructed. */
private boolean constructed = false;
/** The em. */
private transient EntityManagerImpl em;
/** The Constant FINALIZE_FILTER. */
private static final CallbackFilter FINALIZE_FILTER = new CallbackFilter() {
public int accept(Method method) {
if (method.getParameterTypes().length == 0
&& method.getName().equals("finalize")) {
return 1;
} else {
return 0;
}
}
};
/**
* Gets the proxy.
*
* @param entityName
* the entity name
* @param persistentClass
* the persistent class
* @param interfaces
* the interfaces
* @param getIdentifierMethod
* the get identifier method
* @param setIdentifierMethod
* the set identifier method
* @param id
* the id
* @param em
* the em
* @return the proxy
* @throws PersistenceException
* the persistence exception
*/
public static KunderaProxy getProxy(final String entityName,
final Class<?> persistentClass, final Class<?>[] interfaces,
final Method getIdentifierMethod, final Method setIdentifierMethod,
final String id, final EntityManagerImpl em)
throws PersistenceException {
try {
final CglibLazyInitializer instance = new CglibLazyInitializer(
entityName, persistentClass, interfaces, id,
getIdentifierMethod, setIdentifierMethod, em);
final KunderaProxy proxy;
Class factory = getProxyFactory(persistentClass, interfaces);
proxy = getProxyInstance(factory, instance);
instance.constructed = true;
return proxy;
} catch (Throwable t) {
throw new PersistenceException("CGLIB Enhancement failed: "
+ entityName, t);
}
}
/**
* Gets the proxy instance.
*
* @param factory
* the factory
* @param instance
* the instance
* @return the proxy instance
* @throws InstantiationException
* the instantiation exception
* @throws IllegalAccessException
* the illegal access exception
*/
private static KunderaProxy getProxyInstance(Class factory,
CglibLazyInitializer instance) throws InstantiationException,
IllegalAccessException {
KunderaProxy proxy;
try {
Enhancer.registerCallbacks(factory,
new Callback[] { instance, null });
proxy = (KunderaProxy) factory.newInstance();
} finally {
Enhancer.registerCallbacks(factory, null);
}
return proxy;
}
/**
* Gets the proxy factory.
*
* @param persistentClass
* the persistent class
* @param interfaces
* the interfaces
* @return the proxy factory
* @throws PersistenceException
* the persistence exception
*/
public static Class getProxyFactory(Class persistentClass,
Class[] interfaces) throws PersistenceException {
Enhancer e = new Enhancer();
e.setSuperclass(interfaces.length == 1 ? persistentClass : null);
e.setInterfaces(interfaces);
e
.setCallbackTypes(new Class[] { InvocationHandler.class,
NoOp.class, });
e.setCallbackFilter(FINALIZE_FILTER);
e.setUseFactory(false);
e.setInterceptDuringConstruction(false);
return e.createClass();
}
/**
* Instantiates a new cglib lazy initializer.
*
* @param entityName
* the entity name
* @param persistentClass
* the persistent class
* @param interfaces
* the interfaces
* @param id
* the id
* @param getIdentifierMethod
* the get identifier method
* @param setIdentifierMethod
* the set identifier method
* @param em
* the em
*/
private CglibLazyInitializer(final String entityName,
final Class<?> persistentClass, final Class<?>[] interfaces,
final String id, final Method getIdentifierMethod,
final Method setIdentifierMethod, final EntityManagerImpl em) {
this.entityName = entityName;
this.id = id;
this.em = em;
this.persistentClass = persistentClass;
this.getIdentifierMethod = getIdentifierMethod;
this.setIdentifierMethod = setIdentifierMethod;
this.interfaces = interfaces;
}
/*
* @see net.sf.cglib.proxy.InvocationHandler#invoke(java.lang.Object,
* java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (constructed) {
String methodName = method.getName();
int params = args.length;
if (params == 0) {
if (isUninitialized() && method.equals(getIdentifierMethod)) {
return getIdentifier();
}
else if ("getKunderaLazyInitializer".equals(methodName)) {
return this;
}
}
Object target = getImplementation();
try {
final Object returnValue;
if (method.isAccessible()) {
if (!method.getDeclaringClass().isInstance(target)) {
throw new ClassCastException(target.getClass()
.getName());
}
returnValue = method.invoke(target, args);
} else {
if (!method.isAccessible()) {
method.setAccessible(true);
}
returnValue = method.invoke(target, args);
}
return returnValue == target ? proxy : returnValue;
} catch (InvocationTargetException ite) {
throw ite.getTargetException();
}
} else {
// while constructor is running
throw new LazyInitializationException(
"unexpected case hit, method=" + method.getName());
}
}
/* @see com.impetus.kundera.proxy.LazyInitializer#getPersistentClass() */
public final Class<?> getPersistentClass() {
return persistentClass;
}
/**
* Gets the entity name.
*
* @return the entity name {@inheritDoc}
*/
public final String getEntityName() {
return entityName;
}
/**
* Gets the identifier.
*
* @return the identifier {@inheritDoc}
*/
public final String getIdentifier() {
return id;
}
/**
* Sets the identifier.
*
* @param id
* the new identifier {@inheritDoc}
*/
public final void setIdentifier(String id) {
this.id = id;
}
/**
* Checks if is uninitialized.
*
* @return true, if is uninitialized {@inheritDoc}
*/
public final boolean isUninitialized() {
return !initialized;
}
/**
* Gets the entity manager.
*
* @return the entity manager {@inheritDoc}
*/
public final EntityManagerImpl getEntityManager() {
return em;
}
/**
* Unset entity manager.
*
* {@inheritDoc}
*/
public void unsetEntityManager() {
em = null;
}
/**
* Initialize.
*
* @throws PersistenceException
* the persistence exception {@inheritDoc}
*/
public final void initialize() throws PersistenceException {
if (!initialized) {
if (em == null) {
throw new LazyInitializationException(
"could not initialize proxy "
+ persistentClass.getName() + "_" + id
+ " - no EntityManager");
} else if (!em.isOpen()) {
throw new LazyInitializationException(
"could not initialize proxy "
+ persistentClass.getName() + "_" + id
+ " - the owning Session was closed");
} else {
log.debug("Proxy >> Initialization >> "
+ persistentClass.getName() + "_" + id);
// TODO: consider not calling em.find from here. Not sure 'why',
// but something
// doesn't feel right.
target = em.find(persistentClass, id);
initialized = true;
}
}
}
/**
* Return the underlying persistent object, initializing if necessary.
*
* @return the implementation
*/
public final Object getImplementation() {
initialize();
return target;
}
/**
* Getter for property 'target'.
* <p/>
* Same as {@link #getImplementation()} except that this method will not
* force initialization.
*
* @return Value for property 'target'.
*/
protected final Object getTarget() {
return target;
}
/**
* Checks if is unwrap.
*
* @return true, if is unwrap {@inheritDoc}
*/
public boolean isUnwrap() {
return unwrap;
}
/**
* Sets the unwrap.
*
* @param unwrap
* the new unwrap {@inheritDoc}
*/
public void setUnwrap(boolean unwrap) {
this.unwrap = unwrap;
}
}