Version 2

    This code demonstrates custom collection type and custom tuplizer (since Hibernat 3.1).Tuplizer registers custom proxy factory to return proxies using custom serialization.Proxy is desirialized on client with callback to load remote entity.Proxy is serialized/desirailized and initialized in the same JTA transaction using JTA session context.

    Example is tested on JBoss application server.

     

    package example.client;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    import remotelazyloading.client.ClientSession;
    
    /**
     * Filter Example
     * @author Juozas
     *
     */
    public class HibernateFilter implements Filter {
        
        public void doFilter(final ServletRequest request, final ServletResponse response,
                final FilterChain chain) throws IOException, ServletException {
            try {
                ClientSession.execute(
                        new Runnable(){
                            public void run() {
                                try {
                                    chain.doFilter(request,response);
                                } catch(RuntimeException re){
                                    throw re;
                                } catch ( Exception e) {
                                    throw new RuntimeException(e);
                                }
                            }
                        }
                );
            } catch(RuntimeException re){
                throw re;
            } catch (Exception e) {
                throw new ServletException(e);
            }
        }
        public void destroy() {}
        public void init(FilterConfig cfg) throws ServletException {}
    }
    

     

    package example.client;
    
    import javax.naming.InitialContext;
    import javax.rmi.PortableRemoteObject;
    
    import remotelazyloading.client.ClientSession;
    import example.shared.Example;
    import example.shared.ExampleHome;
    import example.shared.ExampleRemote;
    
    /**
     * Simple client application
     * @author Juozas
     *
     */
    public class Main {
        public static void main(String[] args)throws Exception {
            ClientSession.execute(
                    new Runnable(){
                        public void run() {
                            try{
                                
                            InitialContext context = new InitialContext();
                            ExampleHome home = (ExampleHome) PortableRemoteObject.narrow(context.lookup("example/Example"),ExampleHome.class);
                            ExampleRemote remote = home.create();
                            Example example = remote.getLazyEntity();
                            //uninitialized proxy
                            System.out.println(example.getClass());
                            //initializes remote proxy
                            System.out.println(example.getName());
                            //uninitialized collection
                            System.out.println(example.getChildren().getClass());
                            //load collection
                            System.out.println(example.getChildren().toString());
                            
                            }catch(Exception e){
                                throw new RuntimeException(e);
                            }
                        }
                    }
            );
        }
    }
    

     

    package example.shared;
    
    import java.io.Serializable;
    import java.util.Set;
    /**
     * Domain object example
     * @author Juozas
     *
     */
    public class Example implements Serializable {
        
        private static final long serialVersionUID = -283024994343146619L;
        private long id;
        private String name;
        private Set children;
        private Example parent;
        
        public long getId() {
            return id;
        }
        public void setId(long id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Set getChildren() {
            return children;
        }
        public void setChildren(Set children) {
            this.children = children;
        }
        public Example getParent() {
            return parent;
        }
        public void setParent(Example parent) {
            this.parent = parent;
        }
        
        
    }
    

     

    <?xml version="1.0"?>
    <!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping     package="example.shared">
        <class name="Example">
           <tuplizer entity-mode="pojo"
                    class="remotelazyloading.hibernate.TuplizerImpl"/>
            <id name="id">
                <generator class="increment"/>
            </id>
            <property name="name"/>
            <set name="children" collection-type="remotelazyloading.hibernate.RemotableSetType">
                <key column="parent"/>
                <one-to-many class="Example"/>
            </set>
            <many-to-one name="parent" class="Example" column="parent" />
        </class>
    </hibernate-mapping>
    

     

    package example.shared;
    /**
     * Example application home
     * @author Juozas
     *
     */
    public interface ExampleHome extends javax.ejb.EJBHome {
    
        public ExampleRemote create() throws javax.ejb.CreateException, 
        java.rmi.RemoteException;
    
    
    }
    
    package example.shared;
    
    import java.rmi.RemoteException;
    
    import javax.naming.NamingException;
    public interface ExampleRemote extends javax.ejb.EJBObject{
        
        Example getLazyEntity() throws RemoteException,NamingException;
    
    }
    
    

     

    package example.server;
    
    import java.rmi.RemoteException;
    import java.sql.Statement;
    
    import javax.ejb.EJBException;
    import javax.ejb.SessionBean;
    import javax.ejb.SessionContext;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import javax.sql.DataSource;
    
    import org.hibernate.SessionFactory;
    
    import example.shared.Example;
    /**
     * Stateful session bean
     * @author Juozas
     *
     */
    public class ExampleBean implements SessionBean {
        private static final long serialVersionUID = -7085003907759281338L;
        
        public Example getLazyEntity() throws NamingException{
            return (Example) getFactory().getCurrentSession().load(Example.class, new Long(0));
        }
        private SessionFactory getFactory()throws NamingException{
            return  (SessionFactory) new InitialContext().lookup("hibernate/SessionFactory");
        }
        public void ejbCreate(){
            
            try{
                DataSource ds = (DataSource) new InitialContext().lookup("java:/DefaultDS");
                Statement s = ds.getConnection().createStatement();
                try{
                    
                    s.execute(" DROP TABLE EXAMPLE IF EXISTS ");
                    s.execute(" CREATE TABLE EXAMPLE(id int, name varchar(255),parent int)  ");
                    s.execute(" INSERT INTO EXAMPLE(id ,name,parent)VALUES(0,'name0',NULL)  ");
                    s.execute(" INSERT INTO EXAMPLE(id ,name,parent)VALUES(1,'name1',0)     ");
                    s.execute(" INSERT INTO EXAMPLE(id ,name,parent)VALUES(2,'name2',0)     ");
                    
                }finally{
                    
                    s.close();
                }
                
            }catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            
        }
        public void ejbActivate() throws EJBException, RemoteException  {}
        public void ejbPassivate() throws EJBException, RemoteException {}
        public void ejbRemove() throws EJBException, RemoteException {}
        public void setSessionContext(SessionContext context) throws EJBException, RemoteException {}
    
    }
    

     

    package remotelazyloading.client;
    
    import java.rmi.RemoteException;
    
    import javax.ejb.CreateException;
    import javax.ejb.RemoveException;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import javax.rmi.PortableRemoteObject;
    import javax.transaction.Status;
    import javax.transaction.UserTransaction;
    
    import remotelazyloading.shared.HibernateServerHome;
    import remotelazyloading.shared.HibernateServerRemote;
    /**
     * Helper
     * @author Juozas
     *
     */
    public class ClientSession {
        
        private static final ThreadLocal REF = new ThreadLocal();
        
        private static void start(InitialContext context) throws ClassCastException, NamingException, RemoteException, CreateException{
            if(isActive()){
                throw new IllegalStateException("nested sessions are not supported");
            }
            HibernateServerHome  home = (HibernateServerHome) PortableRemoteObject.narrow(context.lookup("hibernate/HibernateServer"),HibernateServerHome.class);
            REF.set( home.create() );
        }
        
        private static boolean isActive(){
            return REF.get() != null;
        }
        
        /*package*/static    HibernateServerRemote getLoader(){
            if(!isActive()){
                throw new IllegalStateException("session is not active");
            }
            return (HibernateServerRemote) REF.get();
        }
        private static void release() throws RemoteException, RemoveException{
            HibernateServerRemote loader = (HibernateServerRemote) REF.get();
            if(loader == null){
                throw new IllegalStateException("session is not started");
            }
            REF.set(null);
            loader.remove();
        }
        public static void execute(Runnable cmd)throws Exception{
            
            InitialContext context = new InitialContext();
            try{
                
                UserTransaction transaction = (UserTransaction) 
                context.lookup("UserTransaction");
                
                transaction.begin();
                try{
                    start(context);
                    try{
                        cmd.run();
                    }finally{
                        release();
                    }
                transaction.commit();
                }finally{
                    if(transaction.getStatus() == Status.STATUS_ACTIVE){    
                        transaction.rollback();
                    }
                }
            }finally{
                context.close();
            }
        }
    }
    

     

    package remotelazyloading.client;
    
    import java.io.ObjectStreamException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.AbstractSet;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Iterator;
    import java.util.Set;
    
    import net.sf.cglib.core.NamingPolicy;
    import net.sf.cglib.core.Predicate;
    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 remotelazyloading.shared.CollectionReference;
    import remotelazyloading.shared.LazyReference;
    /**
     * Client side proxy, it interceps methods calls statefull bean to initialize entity
     * @author Juozas
     *
     */
    public class Proxy {
        
        private static final Class[] CALLBACK_TYPES = new Class[]{ InvocationHandler.class,NoOp.class };
        
        private static final class CollectionProxy extends AbstractSet {
            
            private final CollectionReference reference;
            private Set remote;
    
            private CollectionProxy(CollectionReference reference) {
                super();
                this.reference = reference;
            }
    
            Object writeReplare()throws ObjectStreamException{
                
                if(remote != null){
                    return remote;
                }else {
                    return reference;
                }
            }
    
            private Set load() {
                try{
                    if(remote == null){
                        remote = (Set) ClientSession.getLoader().
                        initializeCollection(reference.getRole(),reference.getId());
                    }
                }catch(Exception e){
                    throw new RuntimeException(e);
                }
                
                return remote;
                
            }
    
            public int size() {
                
                return load().size();
            }
    
            public boolean isEmpty() {
                
                return load().isEmpty();
            }
    
            public Object[] toArray() {
                
                return load().toArray();
            }
    
            public boolean contains(Object o) {
                
                return load().contains(o);
            }
    
            public boolean containsAll(Collection c) {
                
                return load().containsAll(c);
            }
    
            public Iterator iterator() {
                
                return load().iterator();
            }
    
            public Object[] toArray(Object[] a) {
                
                return load().toArray(a);
            }
        }
    
        public interface WriteReplace {
            
            Object writeReplace() throws ObjectStreamException;
        }
        
        private static final class Interceptor implements InvocationHandler {
            
            
            private Object entity;
            private LazyReference ref;
            
            Interceptor(LazyReference ref){
                this.ref = ref;
            }
            
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                
                if( args.length == 0 && 
                        method.getDeclaringClass() == WriteReplace.class &&
                        method.getName().equals("writeReplace")){
                        if(entity != null){
                             return entity;
                        }else {
                             return ref;
                        }
                }
                if(entity == null){
                    entity = ClientSession.getLoader().initializeEntity(ref.getClassName(),ref.getId(),ref.getToken());
                }
                try{
                    
                 return method.invoke(entity,args);
                 
                }catch(InvocationTargetException ite){
                    throw ite;
                }
            }
        }
        
        
        
        public static Object create( final LazyReference ref) throws ClassNotFoundException{
            
            Class cls = Class.forName(ref.getClassName(), false, Thread
                    .currentThread().getContextClassLoader());
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(cls);
            enhancer.setCallbackTypes(CALLBACK_TYPES);
            enhancer.setInterfaces(new Class[]{WriteReplace.class});
            enhancer.setCallbackFilter(new CallbackFilter(){
                public int accept(Method m) {
                    return Modifier.isPublic(m.getModifiers()) ? 0 : 1;
                }
            });
            enhancer.setCallbacks(new Callback[]{ new Interceptor(ref), NoOp.INSTANCE});
            enhancer.setNamingPolicy(
                     new NamingPolicy(){
                        public String getClassName(String arg0, String arg1, Object arg2, Predicate arg3) {
                                return ref.getClassName() + "$ClientProxy";
                        }
                     }        
                    );
            
            return enhancer.create();
        }
    
        public static Set create(final CollectionReference reference) {
            
            Set set = new CollectionProxy(reference);
            
            return Collections.unmodifiableSet(set);
        }
    }
    

     

    package remotelazyloading.shared;
    
    import java.io.ObjectStreamException;
    import java.io.Serializable;
    
    import remotelazyloading.client.Proxy;
    
    public class CollectionReference implements Serializable{
        
        private static final long serialVersionUID = 183925776415464990L;
        private Serializable id;
        private String role;
        
        public Serializable getId() {
            return id;
        }
        public void setId(Serializable id) {
            this.id = id;
        }
        public String getRole() {
            
            return role;
        }
        
        public void setRole(String role){
            this.role = role;
        }
        
        Object readResolve() throws ObjectStreamException{
            return Proxy.create(this);
        }
        
    }
    

     

    package remotelazyloading.shared;
    
    import java.io.ObjectStreamException;
    import java.io.Serializable;
    
    import remotelazyloading.client.Proxy;
    
    /**
     * Uninitialized proxy representation
     * @author Juozas
     *
     */
    public class LazyReference implements Serializable {
    
        private static final long serialVersionUID = 7799616869249915673L;
    
        private Serializable id;
    
        private int token;
    
        private String className;
    
        
        public LazyReference(){
            
        }
        public LazyReference(int token,String className,Serializable id){
            this.token = token;
            this.className = className;
            this.id = id;
        }
    
        /* package */Object readResolve() throws ObjectStreamException,
                ClassNotFoundException {
    
            return Proxy.create(this);
    
        }
    
        public String getClassName() {
            return className;
        }
    
        public void setClassName(String className) {
            this.className = className;
        }
    
        public Serializable getId() {
            return id;
        }
    
        public void setId(Serializable id) {
            this.id = id;
        }
    
        public int getToken() {
            return token;
        }
    
        public void setToken(int token) {
            this.token = token;
        }
    
        
        
    }
    

     

    package remotelazyloading.shared;
    /**
     * Initializer home 
     * @author Juozas
     *
     */
    public interface HibernateServerHome extends javax.ejb.EJBHome {
        public HibernateServerRemote create() throws javax.ejb.CreateException, 
        java.rmi.RemoteException;
    
    }
    
    package remotelazyloading.shared;
    
    /**
     * Remote initializer interface
     * @author Juozas
     *
     */
    public interface HibernateServerRemote extends javax.ejb.EJBObject,Initializer {
    
    }
    
    package remotelazyloading.shared;
    /**
     * Initializer home 
     * @author Juozas
     *
     */
    public interface HibernateServerHome extends javax.ejb.EJBHome {
        public HibernateServerRemote create() throws javax.ejb.CreateException, 
        java.rmi.RemoteException;
    
    }
    
    package remotelazyloading.shared;
    
    import java.io.Serializable;
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    import java.util.Collection;
    
    import javax.naming.NamingException;
    
    import org.hibernate.HibernateException;
    
    public interface Initializer extends Remote {
    
        Object initializeEntity(String className,Serializable id, int token)
        throws RemoteException,
        ClassNotFoundException, 
        HibernateException, 
        NamingException;
        Collection initializeCollection(String role,Serializable id)
        throws RemoteException,
        ClassNotFoundException, 
        HibernateException,
        NamingException;
        
    }
    
    

     

    package remotelazyloading.server;
    
    import java.io.Serializable;
    import java.rmi.RemoteException;
    import java.util.Collection;
    
    import javax.ejb.EJBException;
    import javax.ejb.SessionBean;
    import javax.ejb.SessionContext;
    import javax.naming.NamingException;
    
    import org.hibernate.EntityMode;
    import org.hibernate.HibernateException;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.cfg.Configuration;
    import org.hibernate.engine.CollectionKey;
    import org.hibernate.engine.PersistenceContext;
    import org.hibernate.engine.SessionFactoryImplementor;
    import org.hibernate.engine.SessionImplementor;
    import org.hibernate.persister.collection.CollectionPersister;
    import org.hibernate.proxy.HibernateProxy;
    
    import remotelazyloading.hibernate.RemotableSetType.Wrapper;
    import remotelazyloading.shared.Initializer;
    /**
     * Remote proxy initializer
     * @author Juozas
     *
     */
    public class HibernateServerBean implements SessionBean,Initializer{
    
        private static final long serialVersionUID = -3354876500675131726L;
        private static  SessionFactory factory = new Configuration().configure().buildSessionFactory();
        
        public Object initializeEntity(String className,Serializable id, int token) 
        throws ClassNotFoundException, HibernateException, NamingException{
            Class cls = Class.forName(className,false,Thread.currentThread().getContextClassLoader());
            Session currentSession = factory.getCurrentSession();
            if(System.identityHashCode(currentSession) == token){
                Object obj = currentSession.get(cls,id);
                if(obj == null){
                    throw new IllegalStateException("object not found " + cls + " " + id);
                }
             return ((HibernateProxy)obj).getHibernateLazyInitializer().getImplementation();
            }else {
                throw new IllegalStateException("invalid session id");
            }
        }
    
        public Collection initializeCollection(String role,Serializable id)throws ClassNotFoundException, HibernateException, NamingException{
            
            PersistenceContext context = ((SessionImplementor)factory.getCurrentSession()).getPersistenceContext();
            
            CollectionPersister persister = ((SessionFactoryImplementor)factory).getCollectionPersister(role);
            
            CollectionKey key = new CollectionKey(persister,id,EntityMode.POJO);
            
            Wrapper col = (Wrapper) context.getCollection(key);
            
            if(col == null){
                throw new IllegalStateException("collection not found");
            }
            
            return col.unwrapp();
            
        }
        
        public void ejbActivate() throws EJBException, RemoteException  {}
        public void ejbPassivate() throws EJBException, RemoteException {}
        public void ejbRemove() throws EJBException, RemoteException {}
        public void setSessionContext(SessionContext context) 
        throws EJBException, RemoteException {}
        public void ejbCreate(){
            
        }
    
        
    
        
    
        
    }
    
    

     

    package remotelazyloading.hibernate;
    
    import java.io.Serializable;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import net.sf.cglib.core.NamingPolicy;
    import net.sf.cglib.core.Predicate;
    import net.sf.cglib.proxy.CallbackFilter;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.Factory;
    import net.sf.cglib.proxy.InvocationHandler;
    import net.sf.cglib.proxy.NoOp;
    
    import org.hibernate.HibernateException;
    import org.hibernate.engine.SessionImplementor;
    import org.hibernate.proxy.BasicLazyInitializer;
    import org.hibernate.proxy.HibernateProxy;
    import org.hibernate.type.AbstractComponentType;
    
    import remotelazyloading.shared.LazyReference;
    /**
     * Custom lazy loder
     * @author Juozas
     *
     */
    public class LazyInitializerImpl extends BasicLazyInitializer implements InvocationHandler{
    
     public static final String LOAD_OBJECT_COMMAND = "LO";
        
     private static final Class[] CALLBACK_TYPES = new Class[]{ InvocationHandler.class,NoOp.class };
        
        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;
                }
            }
        };
    
        private LazyInitializerImpl(final String entityName, final Class persistentClass,
                final Class[] interfaces, final Serializable id, final Method getIdentifierMethod,
                final Method setIdentifierMethod, final AbstractComponentType componentIdType,
                final SessionImplementor session) {
            super(
                    entityName,
                    persistentClass,
                    id,
                    getIdentifierMethod,
                    setIdentifierMethod,
                    componentIdType,
                    session 
                );
        }
        
        protected Object serializableProxy() {
            
            if(isUninitialized()){
                
                LazyReference ref = new LazyReference();
                ref.setClassName(getPersistentClass().getName());
                ref.setId(getIdentifier());
                ref.setToken(System.identityHashCode(getSession()));
                
                return ref;
                
            }else{
                
                return getTarget();
            }
        }
    
        
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            
            Object result = invoke( method, args, proxy );
            
            if ( result == INVOKE_IMPLEMENTATION ) {
                
                Object target = getImplementation();
                
                if ( !method.isAccessible() ) method.setAccessible( true );
                Object returnValue;
                try {
                    returnValue = method.invoke( target, args );
                }
                catch (InvocationTargetException ite) {
                    throw ite.getTargetException();
                }
                return returnValue == target ? proxy : returnValue;
            }
            else {
                return result;
            }
        }
    
        public static Class getProxyFactory(final Class persistentClass, Class[] interfaces) {
            
    
                Enhancer en = new Enhancer();
                en.setUseCache( false );
                en.setCallbackTypes( CALLBACK_TYPES );
                en.setCallbackFilter( FINALIZE_FILTER );
                en.setSuperclass( interfaces.length == 1 ? persistentClass : null );
                en.setInterfaces( interfaces );
                en.setNamingPolicy(
                 new NamingPolicy(){
                    public String getClassName(String arg0, String arg1, Object arg2, Predicate arg3) {
                            return persistentClass.getName() + "$ServerProxy";
                    }
                 }        
                );
    
                return en.createClass();
    
        }
    
        public static HibernateProxy getProxy(final Class factory, final String entityName,
                final Class persistentClass, final Class[] interfaces,
                final Method getIdentifierMethod, final Method setIdentifierMethod,
                final AbstractComponentType componentIdType, final Serializable id,
                final SessionImplementor session) throws HibernateException {
            
            final LazyInitializerImpl instance = new LazyInitializerImpl(
                    entityName,
                    persistentClass,
                    interfaces,
                    id,
                    getIdentifierMethod,
                    setIdentifierMethod,
                    componentIdType,
                    session 
                );
            
            final HibernateProxy proxy;
            try {
                proxy = (HibernateProxy) factory.newInstance();
            }
            catch (Exception e) {
                throw new HibernateException( "CGLIB Enhancement failed: " + persistentClass.getName(), e );
            }
            ( (Factory) proxy ).setCallback( 0, instance );
            return proxy;
        }
    }
    

     

    package remotelazyloading.hibernate;
    
    import java.io.Serializable;
    import java.lang.reflect.Method;
    import java.util.Set;
    
    import org.hibernate.HibernateException;
    import org.hibernate.engine.SessionImplementor;
    import org.hibernate.proxy.HibernateProxy;
    import org.hibernate.proxy.ProxyFactory;
    import org.hibernate.type.AbstractComponentType;
    
    /**
     * Custom loader factory
     * @author Juozas
     *
     */
    public class ProxyFactoryImpl implements ProxyFactory {
    
        protected static final Class[] NO_CLASSES = new Class[0];
    
        private Class persistentClass;
        private String entityName;
        private Class[] interfaces;
        private Method getIdentifierMethod;
        private Method setIdentifierMethod;
        private AbstractComponentType componentIdType;
        private Class factory;
    
        public void postInstantiate(
            final String entityName,
            final Class persistentClass,
            final Set interfaces,
            final Method getIdentifierMethod,
            final Method setIdentifierMethod,
            AbstractComponentType componentIdType)
        throws HibernateException {
            this.entityName = entityName;
            this.persistentClass = persistentClass;
            this.interfaces = (Class[]) interfaces.toArray(NO_CLASSES);
            this.getIdentifierMethod = getIdentifierMethod;
            this.setIdentifierMethod = setIdentifierMethod;
            this.componentIdType = componentIdType;
            factory = LazyInitializerImpl.getProxyFactory(persistentClass, this.interfaces);
        }
    
        public HibernateProxy getProxy(Serializable id, SessionImplementor session)
            throws HibernateException {
    
            return LazyInitializerImpl.getProxy(
                    factory, 
                    entityName, 
                    persistentClass, 
                    interfaces, 
                    getIdentifierMethod, 
                    setIdentifierMethod,
                    componentIdType,
                    id, 
                    session
                );
        }
    
    }
    

     

    package remotelazyloading.hibernate;
    
    import java.io.ObjectStreamException;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Set;
    
    import org.hibernate.HibernateException;
    import org.hibernate.collection.PersistentCollection;
    import org.hibernate.collection.PersistentSet;
    import org.hibernate.engine.SessionImplementor;
    import org.hibernate.persister.collection.CollectionPersister;
    import org.hibernate.usertype.UserCollectionType;
    
    import remotelazyloading.shared.CollectionReference;
    
    public class RemotableSetType implements UserCollectionType {
        
        public static class Wrapper extends PersistentSet{
            
            private static final long serialVersionUID = -4399185767996064350L;
        
            public Wrapper(SessionImplementor session, Set set) {
                super(session,set);
            }
    
            public Wrapper(SessionImplementor session) {
                super(session);
            }
            
            public Set unwrapp(){
                read();
                return set;
            }
            
            Object writeReplace()throws ObjectStreamException{
                
                if(wasInitialized()){
                    return set;
                }
                
                CollectionReference ref = new CollectionReference();
                ref.setId(getKey());
                ref.setRole(getRole());
                
                return ref;
                
            }
        }
        
        
        public PersistentCollection instantiate(SessionImplementor session,
                CollectionPersister persister) throws HibernateException {
            
            return new Wrapper(session);
        }
    
        public PersistentCollection wrap(SessionImplementor session,
                Object collection) {
            
             return new Wrapper( session, (Set) collection );
        }
    
        public Iterator getElementsIterator(Object collection) {
            
            return ((Collection)collection).iterator();
        }
    
        public boolean contains(Object collection, Object entity) {
            
            return ((Collection)collection).contains(entity);
        }
    
        public Object indexOf(Object collection, Object entity) {
            
            return null;
        }
    
        public Object replaceElements(Object original, Object target,
                CollectionPersister persister, Object owner, Map copyCache,
                SessionImplementor session) throws HibernateException {
            
            Collection result = (Collection) target;
    
            result.clear();
    
            result.addAll((Collection)original);
            
            return result;
            
        }
    
        public Object instantiate() {
            
            return new HashSet();
        }
    
        public Class getReturnedClass() {
            
            return Set.class;
        }
    
        
    
    }
    

     

    package remotelazyloading.hibernate;
    
    import org.hibernate.mapping.PersistentClass;
    import org.hibernate.proxy.ProxyFactory;
    import org.hibernate.tuple.EntityMetamodel;
    import org.hibernate.tuple.PojoEntityTuplizer;
    /**
     * Custom tuplizer
     * @author Juozas
     *
     */
    public class TuplizerImpl extends PojoEntityTuplizer {
    
        public TuplizerImpl(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
            super(entityMetamodel, mappedEntity);
        }
        
        protected ProxyFactory createProxyFactory(){
            return new ProxyFactoryImpl();
        }
    
    
    }
    

     

    Manifest

    Enterprise-Bean: True
    Class-Path: <jars> 
    

     

     

    Deployment descriptor

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
    <ejb-jar>
       <description></description>
       <display-name></display-name>
       <enterprise-beans>
          <session>
             <description>Server Infrasructure</description>
             <ejb-name>hibernate/HibernateServer</ejb-name>
             <home>remotelazyloading.shared.HibernateServerHome</home>
             <remote>remotelazyloading.shared.HibernateServerRemote</remote>
             <ejb-class>remotelazyloading.server.HibernateServerBean</ejb-class>
             <session-type>Stateful</session-type>
             <transaction-type>Container</transaction-type>
          </session>
          <session>
             <description>Example</description>
             <ejb-name>example/Example</ejb-name>
             <home>example.shared.ExampleHome</home>
             <remote>example.shared.ExampleRemote</remote>
             <ejb-class>example.server.ExampleBean</ejb-class>
             <session-type>Stateful</session-type>
             <transaction-type>Container</transaction-type>
          </session>
       </enterprise-beans>
       <assembly-descriptor>
       <container-transaction>
          <method>
             <ejb-name>hibernate/HibernateServer</ejb-name>
             <method-name>*</method-name>
          </method>
          <trans-attribute>Required</trans-attribute>
       </container-transaction>
       <container-transaction>
          <method>
             <ejb-name>example/Example</ejb-name>
             <method-name>*</method-name>
          </method>
          <trans-attribute>Required</trans-attribute>
       </container-transaction>
       </assembly-descriptor>
    </ejb-jar>
    

     

    Hibernate configuration file

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory name="hibernate/SessionFactory">
            <property name="show_sql">true</property>
            <property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>
            <property name="hibernate.connection.datasource">java:/DefaultDS</property>
            <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
            <property name="hibernate.current_session_context_class">jta</property>
            <property name="hibernate.session_factory_name">hibernate/SessionFactory</property>
            <mapping resource="Example.hbm.xml"/>
        </session-factory>
    </hibernate-configuration>
    

     

    JNDI properties

    java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
    java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
    java.naming.provider.url=jnp://localhost:1099

     

    Ant build file

    <?xml version="1.0" encoding="UTF-8"?>
    <project default="jar" name="RemoteLazyLoading">
        <target name="compile">
            <path id="lib">
                <fileset dir="lib">
                    <include name="**/*.jar" />
                </fileset>
            </path>
            <javac destdir="bin" srcdir="src" classpathref="lib" includes="**/*.java">
            </javac>
        </target>
        <target name="jar" depends="compile" description="RemoteLazyLoading.jar">
            <jar destfile="RemoteLazyLoading.jar" manifest="src/META-INF/MANIFEST.MF">
                <zipfileset dir="src/META-INF" prefix="META-INF">
                    <include name="ejb-jar.xml" />
                </zipfileset>
                <zipfileset dir="src">
                    <include name="hibernate.cfg.xml" />
                    <include name="Example.hbm.xml" />
                </zipfileset>
                <zipfileset dir="bin">
                    <include name="**/hibernate/*.class" />
                    <include name="**/server/*.class" />
                    <include name="**/shared/*.class" />
                </zipfileset>
                <zipfileset dir="lib/server">
                    <include name="*.jar" />
                </zipfileset>
                <zipfileset dir="lib/shared">
                    <include name="*.jar" />
                </zipfileset>
            </jar>
        </target>
        <target name="src" depends="compile" description="src.zip">
            <zip destfile="src.zip">
                <zipfileset dir="src" prefix="src">
                    <include name="**/*.xml" />
                    <include name="**/*.java" />
                    <include name="*.properties" />
                    <include name="*.xml" />
                </zipfileset>
                <zipfileset dir=".">
                    <include name="build.xml" />
                </zipfileset>
            </zip>
        </target>
        <target name="run">
            <path id="classpath">
                <fileset dir="lib">
                    <include name="client/*.jar" />
                    <include name="shared/*.jar" />
                </fileset>
                <pathelement path="bin" />
                <pathelement path="src" />
            </path>
            <java classname="example.client.Main" fork="true" classpathref="classpath" />
        </target>
    </project>