Package org.datanucleus.store.mapped.scostore

Source Code of org.datanucleus.store.mapped.scostore.JoinMapStore

/**********************************************************************
Copyright (c) 2002 Mike Martin (TJDO) and others. All rights reserved.
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.

Contributors:
2003 Andy Jefferson - coding standards
2004 Andy Jefferson - addition of getGetStatement for inherited values
2004 Andy Jefferson - addition of query methods
2004 Marco Schulze  - replaced catch(NotPersistenceCapableException ...) by
                     advance-check via TypeManager.isSupportedType(...)
2004 Andy Jefferson - moved statements to AbstractMapStore
2005 Andy Jefferson - allow for embedded keys/values
    ...
**********************************************************************/
package org.datanucleus.store.mapped.scostore;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ManagedConnection;
import org.datanucleus.ObjectManager;
import org.datanucleus.StateManager;
import org.datanucleus.api.ApiAdapter;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.MapMetaData;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.DatastoreContainerObject;
import org.datanucleus.store.mapped.DatastoreIdentifier;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.expression.LogicSetExpression;
import org.datanucleus.store.mapped.expression.QueryExpression;
import org.datanucleus.store.mapped.expression.ScalarExpression;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.query.IncompatibleQueryElementTypeException;
import org.datanucleus.store.scostore.SetStore;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

/**
* Representation of the backing store for a Map.
* Uses a join table to link the owning container object with the values/keys
* contained in the map.
**/
public abstract class JoinMapStore extends AbstractMapStore
{
    private SetStore keySetStore = null;
    private SetStore valueSetStore = null;
    private SetStore entrySetStore = null;
   
    /**
     * when the element mappings columns can't be part of the primary key
     * by datastore limitations like BLOB types.
     * An adapter mapping is used to be a kind of "index"
     */
    protected final JavaTypeMapping adapterMapping;   

    protected ClassLoaderResolver clr;

    /**
     * Constructor for an Inverse Map.
     * @param mapTable Join table for the Map
     * @param clr The ClassLoaderResolver
     * @param specialization The Specialization
     **/
    public JoinMapStore(DatastoreContainerObject mapTable, ClassLoaderResolver clr,
                        JavaTypeMapping ownerMapping, JavaTypeMapping keyMapping, JavaTypeMapping valueMapping, JavaTypeMapping orderMapping,
                        String keyType, boolean isEmbeddedKey, boolean isSerialisedKey, String valueType, boolean isEmbeddedValue,
                        boolean isSerialisedValue, AbstractMemberMetaData ownerMemberMetaData, AbstractMapStoreSpecialization specialization)
    {
        super(mapTable.getStoreManager(), specialization);
        this.clr = clr;

        this.mapTable = mapTable;
        setOwner(ownerMemberMetaData, clr);

        this.ownerMapping = ownerMapping;
        this.keyMapping = keyMapping;
        this.valueMapping = valueMapping;
        this.adapterMapping = orderMapping;

        this.keyType = keyType;
        this.keysAreEmbedded = isEmbeddedKey;
        this.keysAreSerialised = isSerialisedKey;
        this.valueType = valueType;
        this.valuesAreEmbedded = isEmbeddedValue;
        this.valuesAreSerialised = isSerialisedValue;

        Class key_class=clr.classForName(keyType);
        kmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(key_class, clr);
        Class value_class=clr.classForName(valueType);
        if (ClassUtils.isReferenceType(value_class))
        {
            NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("056066", value_class.getName()));
            vmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForImplementationOfReference(value_class,null,clr);
            if (vmd != null)
            {
                valueType = value_class.getName();
                // TODO This currently just grabs the cmd of the first implementation. It needs to
                // get the cmds for all implementations, so we can have a handle to all possible elements.
                // This would mean changing the SCO classes to have multiple valueTable/valueMapping etc.
                valueTable = storeMgr.getDatastoreClass(vmd.getFullClassName(), clr);
            }
        }
        else
        {
            vmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(value_class, clr);
            if (vmd != null)
            {
                valueType = vmd.getFullClassName();
                if (valuesAreEmbedded)
                {
                    valueTable = null;
                }
                else
                {
                    valueTable = storeMgr.getDatastoreClass(valueType, clr);
                }
            }
        }
    }

    /**
     * Method to put all elements from a Map into our Map.
     * @param sm State Manager for the Map
     * @param m The Map to add
     **/
    public void putAll(StateManager sm, Map m)
    {
        if (m == null || m.size() == 0)
        {
            return;
        }

        HashSet puts = new HashSet();
        HashSet updates = new HashSet();

        Iterator i = m.entrySet().iterator();
        while (i.hasNext())
        {
            Map.Entry e = (Map.Entry)i.next();
            Object key = e.getKey();
            Object value = e.getValue();

            // Make sure the related objects are persisted (persistence-by-reachability)
            validateKeyForWriting(sm, key);
            validateValueForWriting(sm, value);

            // Check if this is a new entry, or an update
            try
            {
                Object oldValue = getValue(sm, key);
                if (oldValue != value)
                {
                    updates.add(e);
                }
            }
            catch (NoSuchElementException nsee)
            {
                if (value != null)
                {
                    puts.add(e);
                }
            }
        }

        boolean batched = allowsBatching();

        // Put any new entries
        if (puts.size() > 0)
        {
            try
            {
                ObjectManager om = sm.getObjectManager();
                ManagedConnection mconn = storeMgr.getConnection(om);
                try
                {
                    // Loop through all entries
                    Iterator iter = puts.iterator();
                    while (iter.hasNext())
                    {
                        // Add the row to the join table
                        Map.Entry entry = (Map.Entry)iter.next();
                        internalPut(sm, mconn, batched, entry.getKey(), entry.getValue(), (!iter.hasNext()));
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (MappedDatastoreException e)
            {
                throw new NucleusDataStoreException(LOCALISER.msg("056016", e.getMessage()), e);
            }
        }

        // Update any changed entries
        if (updates.size() > 0)
        {
            try
            {
                ObjectManager om = sm.getObjectManager();
                ManagedConnection mconn = storeMgr.getConnection(om);
                try
                {
                    // Loop through all entries
                    Iterator iter = updates.iterator();
                    while (iter.hasNext())
                    {
                        // Update the row in the join table
                        Map.Entry entry = (Map.Entry)iter.next();
                        internalUpdate(sm, mconn, batched, entry.getKey(), entry.getValue(), !iter.hasNext());
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (MappedDatastoreException mde)
            {
                throw new NucleusDataStoreException(LOCALISER.msg("056016", mde.getMessage()), mde);
            }
        }
    }

    /**
     * Method to put an item in the Map.
     * @param sm State Manager for the map.
     * @param key The key to store the value against
     * @param value The value to store.
     * @return The value stored.
     **/
    public Object put(StateManager sm, Object key, Object value)
    {
        validateKeyForWriting(sm, key);
        validateValueForWriting(sm, value);

        boolean exists = false;
        Object oldValue;
        try
        {
            oldValue = getValue(sm, key);
            exists = true;
        }
        catch (NoSuchElementException e)
        {
            oldValue = null;
            exists = false;
        }

        if (oldValue != value)
        {
            // Value changed so update the map
            try
            {
                ObjectManager om = sm.getObjectManager();
                ManagedConnection mconn = storeMgr.getConnection(om);
                try
                {
                    if (exists)
                    {
                        internalUpdate(sm, mconn, false, key, value, true);
                    }
                    else
                    {
                        internalPut(sm, mconn, false, key, value, true);
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (MappedDatastoreException e)
            {
                throw new NucleusDataStoreException(LOCALISER.msg("056016", e.getMessage()), e);
            }
        }

        MapMetaData mapmd = ownerMemberMetaData.getMap();
        if (mapmd.isDependentValue() && !mapmd.isEmbeddedValue() && oldValue != null)
        {
            // Delete the old value if it is no longer contained and is dependent
            if (!containsValue(sm, oldValue))
            {
                sm.getObjectManager().deleteObjectInternal(oldValue);
            }
        }

        return oldValue;
    }

    /**
     * Method to remove an item from the map.
     * @param sm State Manager for the map.
     * @param key Key of the item to remove.
     * @return The value that was removed.
     **/
    public Object remove(StateManager sm, Object key)
    {
        if (!validateKeyForReading(sm, key))
        {
            return null;
        }

        Object oldValue;
        boolean exists;
        try
        {
            oldValue = getValue(sm, key);
            exists = true;
        }
        catch (NoSuchElementException e)
        {
            oldValue = null;
            exists = false;
        }

        ObjectManager om = sm.getObjectManager();
        if (exists)
        {
            removeInternal(sm, key);
        }

        MapMetaData mapmd = ownerMemberMetaData.getMap();
        ApiAdapter api = om.getApiAdapter();
        if (mapmd.isDependentKey() && !mapmd.isEmbeddedKey() && api.isPersistable(key))
        {
            // Delete the key if it is dependent
            om.deleteObjectInternal(key);
        }

        if (mapmd.isDependentValue() && !mapmd.isEmbeddedValue() && api.isPersistable(oldValue))
        {
            if (!containsValue(sm, oldValue))
            {
                // Delete the value if it is dependent and is not keyed by another key
                om.deleteObjectInternal(oldValue);
            }
        }

        return oldValue;
    }

  protected abstract void removeInternal(StateManager sm, Object key);

  /**
     * Method to clear the map of all values.
     * @param ownerSM State Manager for the map.
     */
    public void clear(StateManager ownerSM)
    {
        Collection dependentElements = null;
        if (ownerMemberMetaData.getMap().isDependentKey() || ownerMemberMetaData.getMap().isDependentValue())
        {
            // Retain the PC dependent keys/values that need deleting after clearing
            dependentElements = new HashSet();
            ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
            Iterator iter = entrySetStore().iterator(ownerSM);
            while (iter.hasNext())
            {
                Map.Entry entry = (Map.Entry)iter.next();
                MapMetaData mapmd = ownerMemberMetaData.getMap();
                if (api.isPersistable(entry.getKey()) && mapmd.isDependentKey() && !mapmd.isEmbeddedKey())
                {
                    dependentElements.add(entry.getKey());
                }
                if (api.isPersistable(entry.getValue()) && mapmd.isDependentValue() && !mapmd.isEmbeddedValue())
                {
                    dependentElements.add(entry.getValue());
                }
            }
        }
        clearInternal(ownerSM);

        if (dependentElements != null && dependentElements.size() > 0)
        {
            // Delete all dependent objects
            ownerSM.getObjectManager().deleteObjects(dependentElements.toArray());
        }
    }

  protected abstract void clearInternal(StateManager ownerSM);

  /**
     * Accessor for the keys in the Map.
     * @return The keys
     **/
    public synchronized SetStore keySetStore()
    {
        if (keySetStore == null)
        {
            keySetStore = newMapKeySetStore();
        }
        return keySetStore;
    }

    /**
     * Accessor for the values in the Map.
     * @return The values.
     **/
    public synchronized SetStore valueSetStore()
    {
        if (valueSetStore == null)
        {
            valueSetStore = newMapValueSetStore();
        }
        return valueSetStore;
    }

    /**
     * Accessor for the map entries in the Map.
     * @return The map entries.
     */
    public synchronized SetStore entrySetStore()
    {
        if (entrySetStore == null)
        {
            entrySetStore = newMapEntrySetStore();
        }
        return entrySetStore;
    }

    public JavaTypeMapping getAdapterMapping()
    {
        return adapterMapping;
    }

    protected abstract MapKeySetStore newMapKeySetStore();
    protected abstract MapValueSetStore newMapValueSetStore();
    protected abstract MapEntrySetStore newMapEntrySetStore();
    protected abstract void internalUpdate(StateManager ownerSM, ManagedConnection conn, boolean batched,
            Object key, Object value, boolean executeNow) throws MappedDatastoreException;
    protected abstract int[] internalPut(StateManager ownerSM, ManagedConnection conn, boolean batched,
            Object key, Object value, boolean executeNow) throws MappedDatastoreException;

    // ---------------------------- JDOQL Query Methods ------------------------------
    /**
     * Utility to create a join for keys to be used in a containsKey() query.
     * @param stmt The Query Statement
     * @param parentStmt the parent Query Statement. If there is no parent, <code>parentStmt</code> must be equals to <code>stmt</code>
     * @param ownerMapping Mapping for the owner
     * @param ownerTe Table Expression for the owner
     * @param mapTableAlias Alias for the "Map" table.
     * @param filteredKeyType The Class Type for the filtered key
     * @param keyTableAlias The SQL alias to assign to the expression or to the key table.
     * @param keyExpr the expression to the key field. if not provided, obtain the expression of the ID of the table where filteredKeyType is stored
     * @return QueryColumnList with the columns from the key mapping
     **/
    public ScalarExpression joinKeysTo(
                                QueryExpression stmt,
                                QueryExpression parentStmt,
                                JavaTypeMapping ownerMapping,
                                LogicSetExpression ownerTe,
                                DatastoreIdentifier mapTableAlias,
                                Class filteredKeyType,
                                ScalarExpression keyExpr,
                                DatastoreIdentifier keyTableAlias)
    {
        ClassLoaderResolver clr=stmt.getClassLoaderResolver();
        if (!clr.isAssignableFrom(keyType,filteredKeyType) &&
            !clr.isAssignableFrom(filteredKeyType,keyType))
        {
            throw new IncompatibleQueryElementTypeException(keyType, filteredKeyType == null ? null : filteredKeyType.getName());
        }

        // Join the map table on the owner ID column (apply to any unions)
        LogicSetExpression mapTblExpr = stmt.newTableExpression(mapTable, mapTableAlias);
        ScalarExpression ownerMapExpr = this.ownerMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTe);
        if( !parentStmt.hasCrossJoin(mapTblExpr) )
        {
            stmt.crossJoin(mapTblExpr, true);
        }
        stmt.andCondition(ownerExpr.eq(ownerMapExpr),true);
       
        if (storeMgr.getMappedTypeManager().isSupportedMappedType(filteredKeyType.getName()))
        {
            // Key = Non-PC(embedded)
            return keyMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
        }
        else if (keysAreEmbedded || keysAreSerialised)
        {
            // Key = PC(embedded), PC(serialised)
            return keyMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
        }
        else
        {
            // Key = PC
            // Join the key table on the key ID column
            DatastoreClass keyTable = storeMgr.getDatastoreClass(filteredKeyType.getName(), stmt.getClassLoaderResolver());
            JavaTypeMapping keyTableID = keyTable.getIDMapping();

            LogicSetExpression keyTblExpr = stmt.getTableExpression(keyTableAlias);
            if (keyTblExpr==null)
            {
                keyTblExpr = stmt.newTableExpression(keyTable,keyTableAlias);
            }
            ScalarExpression keyMapExpr = keyMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
            if (!parentStmt.hasCrossJoin(keyTblExpr))
            {
                stmt.crossJoin(keyTblExpr, true);
            }
            if( keyExpr == null )
            {
                keyExpr = keyTableID.newScalarExpression(stmt, stmt.getTableExpression(keyTableAlias));
            }
            if( keyExpr.getLogicSetExpression()!= null && !keyTable.equals(keyExpr.getLogicSetExpression().getMainTable()) )
            {
                //keyExpr might express a FK in another to the KEY table
                stmt.andCondition(keyMapExpr.eq(keyExpr),true);
                return this.keyMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
            }
            else
            {
                //keyExpr might be a PK of the KEY table
                ScalarExpression kExpr = keyTableID.newScalarExpression(stmt, stmt.getTableExpression(keyTableAlias));
                stmt.andCondition(keyMapExpr.eq(kExpr),true);
                return kExpr;
            }
        }
    }

    /**
     * Utility to create a join for keys and values to be used in a
     * containsEntry() query.
     * @param stmt The Query Statement to apply the join
     * @param parentStmt the parent Query Statement. If there is no parent, <code>parentStmt</code> must be equals to <code>stmt</code>
     * @param ownerMapping Mapping for the owner
     * @param ownerTe Table Expression for the owner
     * @param mapTableAlias The SQL alias to assign to the expression or to the main table.
     * @param filteredKeyType The Class Type for the filtered key
     * @param filteredValueType The Class Type for the filtered value
     * @param keyExpr the expression to the key field                   
     * @param valExpr Table Expression for the value
     * @param keyTableAlias The SQL alias to assign to the expression or to the key table.
     * @param valueTableAlias The SQL alias to assign to the expression or to the value table.
     * @return an array with 2 elements of QueryColumnList. The first element contains the columns
     *     from the key mapping and the second element the columns from the value mapping
     */
    public ScalarExpression[] joinKeysValuesTo(
                                QueryExpression stmt,
                                QueryExpression parentStmt,
                                JavaTypeMapping ownerMapping,
                                LogicSetExpression ownerTe,
                                DatastoreIdentifier mapTableAlias,
                                Class filteredKeyType,
                                Class filteredValueType,
                                ScalarExpression keyExpr,
                                ScalarExpression valExpr,
                                DatastoreIdentifier keyTableAlias,
                                DatastoreIdentifier valueTableAlias)
    {
        ScalarExpression[] qclKeyValues = new ScalarExpression[2];       

        qclKeyValues[0] = joinKeysTo(stmt, parentStmt, ownerMapping, ownerTe, mapTableAlias, filteredKeyType, keyExpr, keyTableAlias);
        qclKeyValues[1] = joinValuesTo(stmt, parentStmt, ownerMapping, ownerTe, mapTableAlias, filteredValueType, valExpr, valueTableAlias);
        return qclKeyValues;
    }

   
    /**
     * Used as part of the Querying of Maps where a containsValue() is used.
     * @param stmt The Query Statement
     * @param parentStmt the parent Query Statement. If there is no parent, <code>parentStmt</code> must be equals to <code>stmt</code>
     * @param ownerMapping Mapping for the owner
     * @param ownerTe Table Expression for the owner
     * @param mapTableAlias Alias for the "Map" table.
     * @param filteredValueType The Class Type for the filtered value
     * @param valExpr the expression to the value field. if not provided, obtain the expression of the ID of the table where filteredValueType is stored
     * @param valueTableAlias The SQL alias to assign to the expression or to the value table.
     * @return QueryColumnList with the columns from the value mapping
     **/
    public ScalarExpression joinValuesTo(
                                QueryExpression stmt,
                                QueryExpression parentStmt,
                                JavaTypeMapping ownerMapping,
                                LogicSetExpression ownerTe,
                                DatastoreIdentifier mapTableAlias,
                                Class filteredValueType,
                                ScalarExpression valExpr,
                                DatastoreIdentifier valueTableAlias)
    {
        ClassLoaderResolver clr=stmt.getClassLoaderResolver();
        if (!clr.isAssignableFrom(valueType, filteredValueType) &&
            !clr.isAssignableFrom(filteredValueType, valueType))
        {
            throw new IncompatibleQueryElementTypeException(valueType, filteredValueType==null ? null : filteredValueType.getName());
        }

        // Join the map table on the owner ID column
        LogicSetExpression mapTblExpr = stmt.newTableExpression(mapTable, mapTableAlias);
        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt,ownerTe);
        ScalarExpression ownerMapExpr = this.ownerMapping.newScalarExpression(stmt, stmt.getTableExpression(mapTableAlias));
        if( !parentStmt.hasCrossJoin(mapTblExpr) )
        {
            stmt.crossJoin(mapTblExpr, true);
        }
        stmt.andCondition(ownerExpr.eq(ownerMapExpr),true);
       
        if (storeMgr.getMappedTypeManager().isSupportedMappedType(filteredValueType.getName()))
        {
            // Value = Non-PC(embedded)
            return valueMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
        }
        else if (valuesAreEmbedded || valuesAreSerialised)
        {
            // Value = PC(embedded), PC(serialised)
            return valueMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
        }
        else
        {
            // Value = PC
            // Join the value table on the value ID column
            DatastoreClass valueTable=storeMgr.getDatastoreClass(filteredValueType.getName(), stmt.getClassLoaderResolver());
            JavaTypeMapping valueTableID = valueTable.getIDMapping();
            LogicSetExpression valueTblExpr = stmt.getTableExpression(valueTableAlias);
            if (valueTblExpr == null)
            {
                valueTblExpr = stmt.newTableExpression(valueTable,valueTableAlias);
            }
            ScalarExpression valueMapExpr = valueMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
            if (!parentStmt.hasCrossJoin(valueTblExpr))
            {
                stmt.crossJoin(valueTblExpr, true);
            }
            if (valExpr == null)
            {
                valExpr = valueTableID.newScalarExpression(stmt, stmt.getTableExpression(valueTableAlias));
            }
            if (valExpr.getLogicSetExpression() != null &&
                !valueTable.equals(valExpr.getLogicSetExpression().getMainTable()))
            {
                //valExpr might express a FK in another to the VALUE table
                stmt.andCondition(valueMapExpr.eq(valExpr),true);
                return this.valueMapping.newScalarExpression(stmt,stmt.getTableExpression(mapTableAlias));
            }
            else
            {
                //valExpr might be a PK of the VALUE table
                ScalarExpression valueExpr = valueTableID.newScalarExpression(stmt, stmt.getTableExpression(valueTableAlias));
                stmt.andCondition(valueMapExpr.eq(valueExpr),true);
                return valueExpr;
            }
        }
    }
}
TOP

Related Classes of org.datanucleus.store.mapped.scostore.JoinMapStore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.