All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.babyfish.hibernate.proxy.FrozenLazyInitializerImpl Maven / Gradle / Ivy

The newest version!
/*
 * BabyFish, Object Model Framework for Java and JPA.
 * https://github.com/babyfish-ct/babyfish
 *
 * Copyright (c) 2008-2015, Tao Chen
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * Please visit "http://opensource.org/licenses/LGPL-3.0" to know more.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 */
package org.babyfish.hibernate.proxy;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyObject;

import org.babyfish.collection.FrozenContext;
import org.babyfish.hibernate.model.metadata.HibernateMetadatas;
import org.babyfish.hibernate.model.metadata.HibernateObjectModelMetadata;
import org.babyfish.lang.Arguments;
import org.babyfish.lang.Combiner;
import org.babyfish.lang.Combiners;
import org.babyfish.lang.Func;
import org.babyfish.lang.UncheckedException;
import org.babyfish.model.ObjectModel;
import org.babyfish.model.ObjectModelFactory;
import org.babyfish.model.ObjectModelFactoryFactory;
import org.babyfish.model.event.ScalarEvent;
import org.babyfish.model.event.ScalarListener;
import org.babyfish.modificationaware.event.AttributeScope;
import org.babyfish.modificationaware.event.EventAttributeContext;
import org.babyfish.modificationaware.event.ModificationEventHandleException;
import org.babyfish.modificationaware.event.PropertyVersion;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.proxy.pojo.BasicLazyInitializer;

/**
 * @author Tao Chen
 */
public class FrozenLazyInitializerImpl implements FrozenLazyInitializer, MethodHandler, Serializable {
    
    private static final long serialVersionUID = -3172051759799034702L;
    
    private static final Combiner SCALAR_LISTENER_COMBINER = 
            Combiners.of(ScalarListener.class);
    
    private static final Object AK_SCALAR_LISTENER = new Object();

    private static final Object[] EMPTY_ARGS = new Object[0];
    
    private static final Object AK_MODIFIYING_EXECUTED = new Object();
    
    private static final Object AK_FROZENCONTEXT = new Object();
    
    private static final Field GET_IDENTIFIER_METHOD_FIELD;
    
    private static final Field SET_IDENTIFIER_METHOD_FIELD;
    
    private static final Field PERSISTENT_CLASS_FIELD;
    
    protected HibernateProxy owner;
    
    protected LazyInitializer lazyInitializer;
    
    protected transient FrozenContext idFrozenContext;
    
    protected transient HibernateObjectModelMetadata objectModelMetadata;
    
    protected transient ObjectModelFactory omFactory;
    
    protected transient ScalarListener scalarListener;
    
    protected transient Method getIdentifierMethod;
    
    protected transient Method setIdentifierMethod;
    
    private transient Object oldTarget;
    
    private transient boolean disableSetTargetIdentifier;
    
    private transient boolean disableTargetListener;
    
    protected FrozenLazyInitializerImpl(HibernateProxy owner) {
        ProxyObject proxyObject = (ProxyObject)owner;
        MethodHandler handler = proxyObject.getHandler();
        if (!(handler instanceof LazyInitializer)) {
            Arguments.mustBeInstanceOfValue(
                    "((" +
                    ProxyObject.class.getName() +
                    ")owner).getHandler()", 
                    handler, 
                    LazyInitializer.class);
        }
        Class persistentClass = getPersistentClass((BasicLazyInitializer)handler);
        LazyInitializer lazyInitializer = owner.getHibernateLazyInitializer();
        if (lazyInitializer instanceof FrozenLazyInitializer) {
            throw new AssertionError();
        }
        this.owner = owner;
        this.lazyInitializer = lazyInitializer;
        this.objectModelMetadata = HibernateMetadatas.of(persistentClass);
        this.initTransient(persistentClass);
        proxyObject.setHandler(this);
    }
    
    public static FrozenLazyInitializer getFrozenLazyInitializer(
            HibernateProxy hibernateProxy) {
        return getFrozenLazyInitializer(
                hibernateProxy, 
                null);
    }
    
    protected static FrozenLazyInitializer getFrozenLazyInitializer(
            HibernateProxy hibernateProxy,
            Func frozenLazyInitializerCreator) {
        ProxyObject proxyObject = (ProxyObject)hibernateProxy;
        MethodHandler handler = proxyObject.getHandler();
        if (handler instanceof FrozenLazyInitializer) {
            return (FrozenLazyInitializer)handler;
        }
        if (frozenLazyInitializerCreator != null) {
            return frozenLazyInitializerCreator.run(hibernateProxy);
        }
        return new FrozenLazyInitializerImpl(hibernateProxy);
    }
    
    private void readObject(java.io.ObjectInputStream in) 
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        this.initTransient(null);
    }
    
    @SuppressWarnings("unchecked")
    private void initTransient(Class persistentClass) {
        LazyInitializer rawLazyInitializer = this.lazyInitializer;
        if (persistentClass == null) {
            persistentClass = getPersistentClass((BasicLazyInitializer)rawLazyInitializer);
        }
        HibernateObjectModelMetadata objectModelMetadata = HibernateMetadatas.of(persistentClass);
        this.objectModelMetadata = objectModelMetadata;
        this.omFactory = 
                (ObjectModelFactory)
                ObjectModelFactoryFactory
                .factoryOf(objectModelMetadata.getObjectModelClass());
        try {
            this.getIdentifierMethod = (Method)GET_IDENTIFIER_METHOD_FIELD.get(rawLazyInitializer);
            this.setIdentifierMethod = (Method)SET_IDENTIFIER_METHOD_FIELD.get(rawLazyInitializer);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new AssertionError();
        }
        if (!rawLazyInitializer.isUninitialized()) {
            ObjectModel targetOM = 
                    this
                    .omFactory
                    .get(rawLazyInitializer.getImplementation());
            ScalarListener listener = this.new TargetScalarListener();
            targetOM.removeScalarListener(listener);
            targetOM.addScalarListener(listener);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void freezeIdentifier(FrozenContext ctx) {
        this.idFrozenContext = FrozenContext.combine(
                (FrozenContext)this.idFrozenContext, 
                (FrozenContext)ctx);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void unfreezeIdentifier(FrozenContext ctx) {
        this.idFrozenContext = FrozenContext.remove(
                (FrozenContext)this.idFrozenContext, 
                (FrozenContext)ctx);
    }

    @Override
    public void addScalarListener(ScalarListener listener) {
        this.scalarListener = SCALAR_LISTENER_COMBINER.combine(this.scalarListener, listener);
    }

    @Override
    public void removeScalarListener(ScalarListener listener) {
        this.scalarListener = SCALAR_LISTENER_COMBINER.remove(this.scalarListener, listener);
    }

    @Override
    public Serializable getIdentifier() {
        if (!this.isUninitialized()) {
            try {
                return
                        (Serializable)
                        this.getIdentifierMethod
                        .invoke(this.lazyInitializer.getImplementation(), EMPTY_ARGS);
            } catch (IllegalAccessException | IllegalArgumentException ex) {
                throw new AssertionError(ex);
            } catch (InvocationTargetException ex) {
                throw UncheckedException.rethrow(ex.getTargetException());
            }
        }
        return this.lazyInitializer.getIdentifier();
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void setIdentifier(Serializable id) {
        FrozenContext ctx = (FrozenContext)this.idFrozenContext;
        FrozenContext.suspendFreezing(ctx, this.owner);
        try {
            this.setRawIdentifier(id);
        } finally {
            FrozenContext.resumeFreezing(ctx);
        }
    }
    
    private void setRawIdentifier(Serializable id) {
        this.lazyInitializer.setIdentifier(id);
        if (!this.isUninitialized() && !this.disableSetTargetIdentifier) {
            try {
                Method setIdentifierMethod = this.setIdentifierMethod;
                if (!setIdentifierMethod.isAccessible()) {
                    setIdentifierMethod.setAccessible(true);
                }
                this.disableTargetListener = true;
                try {
                    setIdentifierMethod.invoke(this.getImplementation(), new Object[] { id });
                } finally {
                    this.disableTargetListener = false;
                }
            } catch (IllegalAccessException | IllegalArgumentException ex) {
                throw new AssertionError();
            } catch (InvocationTargetException ex) {
                UncheckedException.rethrow(ex.getTargetException());
            }
        }
    }

    @Override
    public void setImplementation(Object target) {
        LazyInitializer rawLazyInitializer = this.lazyInitializer;
        Object oldTarget = 
                rawLazyInitializer.isUninitialized() ? 
                        null : 
                        rawLazyInitializer.getImplementation();
        if (target != oldTarget) {
            ScalarListener listener = this.new TargetScalarListener();
            if (oldTarget != null) {
                this.omFactory.get(oldTarget).removeScalarListener(listener);
            }
            rawLazyInitializer.setImplementation(target);
            this.oldTarget = target;
            if (target != null) {
                ObjectModel targetOM = this.omFactory.get(target);
                targetOM.removeScalarListener(listener); //remove the duplicated listener.
                targetOM.addScalarListener(listener);
            }
        }
    }
    
    @Override
    public void initialize() throws HibernateException {
        LazyInitializer rawLazyInitializer = this.lazyInitializer;
        Object oldTarget = 
                rawLazyInitializer.isUninitialized() ? 
                        null : 
                        rawLazyInitializer.getImplementation();
        rawLazyInitializer.initialize();
        Object target = rawLazyInitializer.getImplementation();
        if (oldTarget != target) {
            ScalarListener listener = this.new TargetScalarListener();
            if (oldTarget != null) {
                this.omFactory.get(oldTarget).removeScalarListener(listener);
            }
            this.oldTarget = target;
            if (target != null) {
                ObjectModel targetOM = this.omFactory.get(target);
                targetOM.removeScalarListener(listener); //remove the duplicated listener.
                targetOM.addScalarListener(listener);
            }
        }
    }

    @Override
    public Object getImplementation() {
        Object oldTarget = this.oldTarget;
        Object target = this.lazyInitializer.getImplementation();
        if (oldTarget != target) {
            ScalarListener listener = this.new TargetScalarListener();
            if (oldTarget != null) {
                this.omFactory.get(oldTarget).removeScalarListener(listener);
            }
            this.oldTarget = target;
            if (target != null) {
                ObjectModel targetOM = this.omFactory.get(target);
                targetOM.removeScalarListener(listener); //remove the duplicated listener.
                targetOM.addScalarListener(listener);
            }
        }
        return target;
    }

    @Override
    public Object getImplementation(SessionImplementor session)
            throws HibernateException {
        Object oldTarget = this.oldTarget;
        Object target = this.lazyInitializer.getImplementation(session);
        if (oldTarget != target) {
            ScalarListener listener = this.new TargetScalarListener();
            if (oldTarget != null) {
                this.omFactory.get(oldTarget).removeScalarListener(listener);
            }
            this.oldTarget = target;
            if (target != null) {
                ObjectModel targetOM = this.omFactory.get(target);
                targetOM.removeScalarListener(listener); //remove the duplicated listener.
                targetOM.addScalarListener(listener);
            }
        }
        return target;
    }

    @Override
    public String getEntityName() {
        return this.lazyInitializer.getEntityName();
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Class getPersistentClass() {
        return this.lazyInitializer.getPersistentClass();
    }

    @Override
    public boolean isUninitialized() {
        return this.lazyInitializer.isUninitialized();
    }

    @Override
    public boolean isReadOnlySettingAvailable() {
        return this.lazyInitializer.isReadOnlySettingAvailable();
    }

    @Override
    public boolean isReadOnly() {
        return this.lazyInitializer.isReadOnly();
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        this.lazyInitializer.setReadOnly(readOnly);
    }

    @Override
    public SessionImplementor getSession() {
        return this.lazyInitializer.getSession();
    }

    @Override
    public void setSession(SessionImplementor session)
            throws HibernateException {
        this.lazyInitializer.setSession(session);
    }

    @Override
    public void unsetSession() {
        this.lazyInitializer.unsetSession();
    }

    @Override
    public void setUnwrap(boolean unwrap) {
        this.lazyInitializer.setUnwrap(unwrap);
    }

    @Override
    public boolean isUnwrap() {
        return this.lazyInitializer.isUnwrap();
    }
    
    @Override
    public Object invoke(
            Object self, 
            Method thisMethod, 
            Method proceed, 
            Object[] args) throws Throwable {
        if (thisMethod.getName().equals("getHibernateLazyInitializer")) {
            return this;
        } else if (thisMethod.equals(this.getIdentifierMethod)) {
            //Very important!!!
            //Hibernate invoke thisMethod.invoke(target, args) here if the proxy is loaded.
            //But babyfish do the same operation in this.getIdentifier(),
            //Becasue the behaviors of getIdentfier() and setIdentifier(Serializable)
            //must be same. (see the comment of next else if)
            return this.getIdentifier();
        } else if (thisMethod.equals(this.setIdentifierMethod)) {
            //Very important!!!
            //Hibernate invoke thisMethod.invoke(target, args) here.
            //But babyfish do the same operation in this.setIdentifier(Serializable),
            //this is very important, because the "ctx.resumeFreezing()" in this.setIdentifier()
            //need the id of the target if the proxy is loaded.
            this.setIdentifier((Serializable)args[0]);
        }
        return ((MethodHandler)this.lazyInitializer).invoke(
                self, 
                thisMethod, 
                proceed, 
                args);
    }
    
    protected void executeModifying(ScalarEvent e) {
        Throwable finalThrowable = null;
        try {
            this.onModifying(e);    
        } catch (Throwable ex) {
            finalThrowable = ex;
        }
        try {
            this.raiseModifying(e);
        } catch (Throwable ex) {
            if (finalThrowable == null) {
                finalThrowable = ex;
            }
        }
        if (finalThrowable != null) {
            throw new ModificationEventHandleException(false, e, finalThrowable);
        }
    }

    protected void executeModified(ScalarEvent e) {
        Throwable finalThrowable = null;
        try {
            this.raiseModified(e);
        } catch (Throwable ex) {
            finalThrowable = ex;
        }
        try {
            this.onModified(e);
        } catch (Throwable ex) {
            if (finalThrowable == null) {
                finalThrowable = ex;
            }
        }
        if (finalThrowable != null) {
            throw new ModificationEventHandleException(true, e, finalThrowable);
        }
    }

    protected void onModifying(ScalarEvent e) throws Throwable {
        
    }

    protected void onModified(ScalarEvent e) throws Throwable {
        
    }

    protected void raiseModifying(ScalarEvent e) throws Throwable {
        ScalarListener scalarListener = this.scalarListener;
        if (scalarListener != null) {
            e
            .getAttributeContext(AttributeScope.LOCAL)
            .addAttribute(AK_SCALAR_LISTENER, scalarListener);
            scalarListener.modifying(e);
        }
    }

    protected void raiseModified(ScalarEvent e) throws Throwable {
        ScalarListener scalarListener = 
            (ScalarListener)
            e
            .getAttributeContext(AttributeScope.LOCAL)
            .removeAttribute(AK_SCALAR_LISTENER);
        if (scalarListener != null) {
            scalarListener.modified(e);
        }
    }
    
    private static Class getPersistentClass(BasicLazyInitializer basicLazyInitializer) {
        try {
            return (Class)PERSISTENT_CLASS_FIELD.get(basicLazyInitializer);
        } catch (IllegalAccessException ex) {
            throw new AssertionError(ex);
        }
    }
    
    private final class TargetScalarListener implements ScalarListener {

        @SuppressWarnings("unchecked")
        @Override
        public void modifying(ScalarEvent e) {
            FrozenLazyInitializerImpl declaring = FrozenLazyInitializerImpl.this;
            if (!declaring.disableTargetListener) {
                ObjectModelFactory omFactory = declaring.omFactory;
                LazyInitializer rawLazyInitializer = declaring.lazyInitializer;
                EventAttributeContext localAttributeContext = e.getAttributeContext(AttributeScope.LOCAL);
                if (rawLazyInitializer.isUninitialized() ||
                        omFactory.get(rawLazyInitializer.getImplementation()) != e.getSource()) {
                    ((ObjectModel)e.getSource()).removeScalarListener(this);
                    return;
                }
                localAttributeContext.addAttribute(AK_MODIFIYING_EXECUTED, true);
                if (e.getProperty() == declaring.objectModelMetadata.getEntityIdProperty()) {
                    FrozenContext ctx = (FrozenContext)declaring.idFrozenContext;
                    localAttributeContext.addAttribute(AK_FROZENCONTEXT, ctx);
                    FrozenContext.suspendFreezing(ctx, declaring.owner);
                }
                ScalarListener scalarListener = declaring.scalarListener;
                if (scalarListener != null) {
                    declaring.executeModifying(e.dispatch(declaring.omFactory.get(declaring.owner)));
                }
            }
        }

        @Override
        public void modified(ScalarEvent e) {
            EventAttributeContext localAttributeContext = e.getAttributeContext(AttributeScope.LOCAL);
            if (localAttributeContext.getAttribute(AK_MODIFIYING_EXECUTED) == null) {
                return;
            }
            FrozenLazyInitializerImpl declaring = FrozenLazyInitializerImpl.this;
            if (e.getProperty() == declaring.objectModelMetadata.getEntityIdProperty()) {
                try {
                    declaring.disableSetTargetIdentifier = true;
                    try {
                        declaring.setIdentifier((Serializable)e.getScalar(PropertyVersion.ATTACH));
                    } finally {
                        declaring.disableSetTargetIdentifier = false;
                    }
                } finally {
                    FrozenContext ctx = (FrozenContext)localAttributeContext.getAttribute(AK_FROZENCONTEXT);
                    FrozenContext.resumeFreezing(ctx);
                }
            }
            ScalarEvent dispatchedEvent = e.getDispathcedEvent(true);
            if (dispatchedEvent != null) {
                declaring.executeModified(dispatchedEvent);
            }
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(FrozenLazyInitializerImpl.this);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof TargetScalarListener)) {
                return false;
            }
            TargetScalarListener other = (TargetScalarListener)obj;
            return FrozenLazyInitializerImpl.this == other.owner();
        }
        
        private Object owner() {
            return FrozenLazyInitializerImpl.this;
        }
        
    }
    
    static {
        Field field;
        try {
            field = BasicLazyInitializer.class.getDeclaredField("getIdentifierMethod");
        } catch (NoSuchFieldException ex) {
            throw new AssertionError(ex);
        }
        field.setAccessible(true);
        GET_IDENTIFIER_METHOD_FIELD = field;
        try {
            field = BasicLazyInitializer.class.getDeclaredField("setIdentifierMethod");
        } catch (NoSuchFieldException ex) {
            throw new AssertionError(ex);
        }
        field.setAccessible(true);
        SET_IDENTIFIER_METHOD_FIELD = field;
        try {
            field = BasicLazyInitializer.class.getDeclaredField("persistentClass");
        } catch (NoSuchFieldException ex) {
            throw new AssertionError(ex);
        }
        field.setAccessible(true);
        PERSISTENT_CLASS_FIELD = field;
    }
}