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

org.babyfish.hibernate.collection.spi.persistence.MapBasePersistence 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.collection.spi.persistence;

import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.babyfish.collection.ArrayList;
import org.babyfish.collection.HashMap;
import org.babyfish.collection.MACollections;
import org.babyfish.collection.MAMap;
import org.babyfish.collection.TreeMap;
import org.babyfish.collection.UnifiedComparator;
import org.babyfish.collection.XCollection;
import org.babyfish.collection.XMap;
import org.babyfish.lang.Arguments;
import org.babyfish.lang.IllegalProgramException;
import org.babyfish.lang.Ref;
import org.babyfish.util.LazyResource;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.internal.JoinHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.loader.CollectionAliases;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.type.Type;

/**
 * @author Tao Chen
 */
public abstract class MapBasePersistence extends AbstractBasePersistence {

    private static final long serialVersionUID = -5020841795509944819L;
    
    private static final LazyResource LAZY_COMMON_RESOURCE = 
            LazyResource.of(CommonResource.class);
    
    private static final String MIDDLE_TABLE_ALIAS = "babyfish_mt_alias_";
    
    protected abstract MAMap getBase();
    
    protected abstract void initBase(MAMap base);
    
    protected abstract XMap getQueuedOrphans();
    
    protected abstract void preformQueuedOrphans();

    @SuppressWarnings("unchecked")
    @Override
    public V getElement(Object entry) {
        return ((Entry)entry).getValue();
    }

    @Override
    public Iterator> entries(CollectionPersister persister) {
        return MACollections.unmodifiable(this.getBase().entrySet()).iterator();
        //Not MACollections.unmodifiable(this.getBase().entrySet().iterator());
        //Entry is readonly to so that Entry.setValue can not work normally.
    }

    @SuppressWarnings("unchecked")
    @Override
    public V readFrom(
            ResultSet rs, 
            CollectionPersister persister,
            CollectionAliases descriptor, 
            Object owner) throws HibernateException, SQLException {
        V value = (V)persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() );
        K key = (K)persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() );
        if ( value != null ) {
            this.getBase().put(key, value);
        }
        return value;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Collection getOrphans(Serializable snapshot, String entityName)
            throws HibernateException {
        XMap sn = (XMap)snapshot;
        return getOrphans(sn.values(), this.getBase().values(), entityName, getSession());
    }

    @Override
    public Iterator queuedAdditionIterator() {
        return MACollections.emptySet().iterator();
    }

    @Override
    public Collection getQueuedOrphans(String entityName) {
        XMap map = this.getQueuedOrphans();
        if (!map.isEmpty()) {
            XCollection removals = new ArrayList(map.valueUnifiedComparator(), map.size());
            for (V value : map.values()) {
                removals.add(value);
            }
            return getOrphans(removals, MACollections.emptySet(), entityName, this.getSession());
        }
        else {
            return MACollections.emptySet();
        }
    }

    @Override
    public boolean empty() {
        return this.getBase().isEmpty();
    }

    @Override
    public void beginRead() {
        this.setInitializing(true);
    }

    @Override
    public boolean endRead() {
        return this.afterInitialize();
    }

    @Override
    public boolean afterInitialize() {
        setInitialized();
        //do this bit after setting initialized to true or it will recurse
        if (this.hasQueuedOperations()) {
            this.preformQueuedOrphans();
            //cacheSize = -1;
            return false;
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void initializeFromCache(
            CollectionPersister persister, 
            Serializable disassembled, 
            Object owner) throws HibernateException {
        Serializable[] array = (Serializable[])disassembled;
        int size = array.length;
        beforeInitialize(persister, size);
        MAMap baseMap = this.getBase();
        for ( int i = 0; i < size; i+=2 ) {
            baseMap.put(
                    (K)persister.getIndexType().assemble(array[i], getSession(), owner),
                    (V)persister.getElementType().assemble(array[i+1], getSession(), owner));
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public K getIndex(Object entry, int i, CollectionPersister persister) {
        return ((Entry)entry).getKey();
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object getSnapshotElement(Object entry, int i) {
        XMap sn = (XMap)getSnapshot();
        return sn.get(((Entry)entry).getKey());
    }

    @SuppressWarnings("unchecked")
    @Override
    public void beforeInitialize(CollectionPersister persister, int anticipatedSize) {
        Object instantiate = persister.getCollectionType().instantiate(anticipatedSize);
        if (!(instantiate instanceof MAMap)) {
            throw new IllegalProgramException(
                    LAZY_COMMON_RESOURCE.get().illegalInstantiate(
                            persister.getCollectionType().getClass(), 
                            MAMap.class
                    )
            );
        }
        this.initBase((MAMap)instantiate);
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException {
        Type elementType = persister.getElementType();
        XMap snapshotMap = (XMap)this.getSnapshot();
        MAMap baseMap = this.getBase();
        if (snapshotMap.size() != baseMap.size()) {
            return false;
        }
        Iterator> iter = baseMap.entrySet().iterator();
        while (iter.hasNext()) {
            Entry entry = iter.next();
            if (elementType.isDirty(entry.getValue(), snapshotMap.get(entry.getKey()), this.getSession())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean isSnapshotEmpty(Serializable snapshot) {
        return ((XMap)snapshot).isEmpty();
    }

    @Override
    public Serializable disassemble(CollectionPersister persister) throws HibernateException {
        MAMap baseMap = this.getBase();
        Serializable[] result = new Serializable[baseMap.size() << 1];
        Iterator> iter = baseMap.entrySet().iterator();
        int i=0;
        while (iter.hasNext()) {
            Entry e = iter.next();
            result[i++] = persister.getIndexType().disassemble(e.getKey(), getSession(), null);
            result[i++] = persister.getElementType().disassemble(e.getValue(), getSession(), null);
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Serializable getSnapshot(CollectionPersister persister) throws HibernateException {
        MAMap baseMap = this.getBase();
        Map clonedMap;
        UnifiedComparator keyUnifiedComparator = baseMap.keyUnifiedComparator();
        if (keyUnifiedComparator.comparator() != null) {
            clonedMap = new TreeMap(
                    keyUnifiedComparator.comparator(),
                    baseMap.valueUnifiedComparator());
        } else {
            clonedMap = new HashMap(
                    keyUnifiedComparator.equalityComparator(),
                    baseMap.valueUnifiedComparator(),
                    baseMap.size() + 1, 
                    1.F);
        }
        Iterator> iter = baseMap.entrySet().iterator();
        while ( iter.hasNext() ) {
            Entry e = (Entry)iter.next();
            final V copy = (V)persister.getElementType().deepCopy(
                    e.getValue(), persister.getFactory());
            clonedMap.put(e.getKey(), copy);
        }
        return (Serializable)clonedMap;
    }

    @Override
    public boolean entryExists(Object entry, int i) {
        return ((Entry)entry).getValue() != null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean needsInserting(Object entry, int i, Type elemType) throws HibernateException {
        final XMap snapshot = (XMap)this.getSnapshot();
        Entry e = (Entry) entry;
        return e.getValue() != null && snapshot.get(e.getKey()) == null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean needsUpdating(Object entry, int i, Type elemType) throws HibernateException {
        final XMap sn = (XMap)this.getSnapshot();
        Entry e = (Entry)entry;
        V snValue = sn.get(e.getKey());
        return 
                e.getValue() != null && 
                snValue!=null && 
                elemType.isDirty(snValue, e.getValue(), this.getSession());
    }

    @Override
    public boolean isRowUpdatePossible() {
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Iterator getDeletes(CollectionPersister persister,
            boolean indexIsFormula) throws HibernateException {
        MAMap baseMap = this.getBase();
        List deletes = new ArrayList();
        Iterator> iter = ((XMap)this.getSnapshot()).entrySet().iterator();
        while (iter.hasNext()) {
            Entry e = (Entry)iter.next();
            K key = e.getKey();
            if (e.getValue() != null && baseMap.get(key) == null) {
                deletes.add(indexIsFormula ? e.getValue() : key);
            }
        }
        return deletes.iterator();
    }

    @Override
    public boolean isWrapper(Object collection) {
        return this.getBase() == collection;
    }

    @SuppressWarnings("unchecked")
    public Ref visionallyRead(K key) {
        
        Arguments.mustNotBeNull("key", key);
        String role = this.getNonNullRole();
        
        SessionImplementor session = this.getSession();
        if (session == null || !session.isOpen() || !session.isConnected()) {
            return null;
        }
        
        SessionFactoryImplementor sessionFactory = session.getFactory();
        QueryableCollection collection = (QueryableCollection)sessionFactory.getCollectionPersister(role);
        EntityPersister elementPersister = collection.getElementPersister();
        
        String[] indexNames = collection.getIndexColumnNames();
        if (indexNames == null || indexNames[0] == null) {
            indexNames = collection.getIndexFormulas();
        }
        CriteriaImpl criteria = new CriteriaImpl(elementPersister.getEntityName(), session);
        
        //ownerKey, not ownerId
        Object ownerKey = collection.getCollectionType().getKeyOfOwner(this.getOwner(), session);
        //In Hibernate, isOneToMany means that there is no middle table
        //The @OneToMany of JPA with middle table is consider as many-to-many in Hibernate
        if (sessionFactory.getCollectionPersister(role).isOneToMany()) {
            String[] joinOwnerColumns = collection.getKeyColumnNames();
            StringBuilder sqlBuilder = new StringBuilder();
            for (int i = 0; i < joinOwnerColumns.length; i++) {
                if (i != 0) {
                    sqlBuilder.append(" and ");
                }
                sqlBuilder
                .append("{alias}.")
                .append(joinOwnerColumns[i])
                .append(" = ?");
            }
            criteria.add(
                    Restrictions.sqlRestriction(
                            sqlBuilder.toString(),
                            ownerKey,
                            collection.getKeyType()));
            
            sqlBuilder = new StringBuilder();
            for (int i = 0; i < indexNames.length; i++) {
                if (i != 0) {
                    sqlBuilder.append(" and ");
                }
                sqlBuilder
                .append("{alias}.")
                .append(indexNames[i])
                .append(" = ?");
            }
            criteria.add(
                    Restrictions.sqlRestriction(
                            sqlBuilder.toString(),
                            key,
                            collection.getIndexType()));
        } else {
            String lhsPropertyName = collection.getCollectionType().getLHSPropertyName();
            int lhsPropertyIndex = -1;
            if (lhsPropertyName != null)  {
                String[] propertyNames = collection.getOwnerEntityPersister().getPropertyNames();
                for (int i = propertyNames.length - 1; i >= 0; i--) {
                    if (propertyNames[i].equals(lhsPropertyName)) {
                        lhsPropertyIndex = i;
                        break;
                    }
                }
            }
            String[] lhsColumnNames = JoinHelper.getLHSColumnNames(
                    collection.getCollectionType(), 
                    lhsPropertyIndex,
                    (OuterJoinLoadable)elementPersister, 
                    sessionFactory);
            String[] joinElementColumnNames = collection.getElementColumnNames();
            String[] joinOwnerColumnNames = collection.getKeyColumnNames();
            
            StringBuilder subQueryBuilder = new StringBuilder();
            subQueryBuilder
            .append("exists(select * from ")
            .append(collection.getTableName())
            .append(" as ")
            .append(MIDDLE_TABLE_ALIAS)
            .append(" where ");
            for (int i = 0; i < joinElementColumnNames.length; i++) {
                if (i != 0) {
                    subQueryBuilder.append(" and ");
                }
                subQueryBuilder
                .append("{alias}.")
                .append(lhsColumnNames[i])
                .append(" = ")
                .append(MIDDLE_TABLE_ALIAS)
                .append(".")
                .append(joinElementColumnNames[i]);
            }
            for (int i = 0; i < joinOwnerColumnNames.length; i++) {
                subQueryBuilder
                .append(" and ")
                .append(MIDDLE_TABLE_ALIAS)
                .append('.')
                .append(joinOwnerColumnNames[i])
                .append(" = ?");
            }
            for (int i = 0; i < indexNames.length; i++) {
                subQueryBuilder
                .append(" and ")
                .append(MIDDLE_TABLE_ALIAS)
                .append('.')
                .append(indexNames[i])
                .append(" = ?");
            }
            subQueryBuilder.append(')');
            criteria.add(
                    Restrictions.sqlRestriction(
                            subQueryBuilder.toString(),
                            new Object[] { ownerKey, key },
                            new Type[] { collection.getKeyType(), collection.getIndexType() }));
        }
        FlushMode oldFlushMode = session.getFlushMode();
        session.setFlushMode(FlushMode.MANUAL);
        try {
            return new Ref((V)criteria.uniqueResult());
        } finally {
            session.setFlushMode(oldFlushMode);
        }
    }
}