Package org.jpox.store.rdbms.scostore

Source Code of org.jpox.store.rdbms.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.jpox.store.rdbms.scostore;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

import org.jpox.ClassLoaderResolver;
import org.jpox.ManagedConnection;
import org.jpox.ObjectManager;
import org.jpox.StateManager;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.MapMetaData;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.expression.BooleanExpression;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.Mappings;
import org.jpox.store.mapped.mapping.SerialisedMapping;
import org.jpox.store.query.IncompatibleQueryElementTypeException;
import org.jpox.store.rdbms.SQLController;
import org.jpox.store.rdbms.SQLWarnings;
import org.jpox.store.rdbms.mapping.RDBMSMapping;
import org.jpox.store.rdbms.query.UnionIteratorStatement;
import org.jpox.store.rdbms.table.JoinTable;
import org.jpox.store.rdbms.table.MapTable;
import org.jpox.store.scostore.SetStore;
import org.jpox.util.JPOXLogger;

/**
* 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.
*
* @version $Revision: 1.45 $
**/
public class JoinMapStore extends AbstractMapStore
{
    private final String putStmt;
    private final String updateStmt;
    private final String removeStmt;
    private final String clearStmt;

    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
     **/
    public JoinMapStore(MapTable mapTable, ClassLoaderResolver clr)
    {
        super(mapTable.getStoreManager());
        this.clr = clr;

        this.mapTable = mapTable;
        setOwnerMemberMetaData(mapTable.getOwnerFieldMetaData());

        ownerMapping = mapTable.getOwnerMapping();
        keyMapping = mapTable.getKeyMapping();
        valueMapping = mapTable.getValueMapping();
        adapterMapping = mapTable.getOrderMapping();

        keyType = mapTable.getKeyType();
        keysAreEmbedded = mapTable.isEmbeddedKey();
        keysAreSerialised = mapTable.isSerialisedKey();
        valueType = mapTable.getValueType();
        valuesAreEmbedded = mapTable.isEmbeddedValue();
        valuesAreSerialised = mapTable.isSerialisedValue();

        Class key_class=clr.classForName(keyType);
        kmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(key_class, clr);
        Class value_class=clr.classForName(valueType);
        if (storeMgr.getOMFContext().getTypeManager().isReferenceType(value_class))
        {
            JPOXLogger.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);
                }
            }
        }

        // Generate the statements
        initialiseStatements();
        putStmt = getPutStmt();
        updateStmt = getUpdateStmt();
        removeStmt = getRemoveStmt();
        clearStmt = getClearStmt();
    }

    /**
     * Generate statement to add an item to the Map.
     * Adds a row to the link table, linking container with value object.
     * <PRE>
     * INSERT INTO MAPTABLE (VALUECOL, OWNERCOL, KEYCOL)
     * VALUES (?, ?, ?)
     * </PRE>
     * @return Statement to add an item to the Map.
     */
    private String getPutStmt()
    {
        StringBuffer stmt = new StringBuffer();
        stmt.append("INSERT INTO ");
        stmt.append(mapTable.toString());
        stmt.append(" (");
        for (int i=0; i<valueMapping.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                stmt.append(",");
            }
            stmt.append(valueMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
        }

        for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
        {
            stmt.append(",");
            stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
        }
        if (adapterMapping != null)
        {
            for (int i=0; i<adapterMapping.getNumberOfDatastoreFields(); i++)
            {
                stmt.append(",");
                stmt.append(adapterMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            }
        }
        for (int i=0; i<keyMapping.getNumberOfDatastoreFields(); i++)
        {
            stmt.append(",");
            stmt.append(keyMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
        }

        stmt.append(") VALUES (");
        for (int i=0; i<valueMapping.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                stmt.append(",");
            }
            stmt.append(((RDBMSMapping)valueMapping.getDataStoreMapping(i)).getInsertionInputParameter());
        }

        for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
        {
            stmt.append(",");
            stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getInsertionInputParameter());
        }
        if (adapterMapping != null)
        {
            for (int i=0; i<adapterMapping.getNumberOfDatastoreFields(); i++)
            {
                stmt.append(",");
                stmt.append(((RDBMSMapping)adapterMapping.getDataStoreMapping(i)).getInsertionInputParameter());
            }
        }
        for (int i=0; i<keyMapping.getNumberOfDatastoreFields(); i++)
        {
            stmt.append(",");
            stmt.append(((RDBMSMapping)keyMapping.getDataStoreMapping(i)).getInsertionInputParameter());
        }
        stmt.append(") ");

        return stmt.toString();
    }

    /**
     * Generate statement to update an item in the Map.
     * Updates the link table row, changing the value object for this key.
     * <PRE>
     * UPDATE MAPTABLE
     * SET VALUECOL=?
     * WHERE OWNERCOL=?
     * AND KEYCOL=?
     * </PRE>
     * @return Statement to update an item in the Map.
     */
    private String getUpdateStmt()
    {
        StringBuffer stmt = new StringBuffer();
        stmt.append("UPDATE ");
        stmt.append(mapTable.toString());
        stmt.append(" SET ");
        for (int i=0; i<valueMapping.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                stmt.append(",");
            }
            stmt.append(valueMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)valueMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }
        stmt.append(" WHERE ");
        for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                stmt.append(" AND ");
            }
            stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }
        for (int i=0; i<keyMapping.getNumberOfDatastoreFields(); i++)
        {
            stmt.append(" AND ");
            stmt.append(keyMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)keyMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }
        return stmt.toString();
    }

    /**
     * Generate statement to remove an item from the Map.
     * Deletes the link from the join table, leaving the value object in its
     * own table.
     * <PRE>
     * DELETE FROM MAPTABLE
     * WHERE OWNERCOL=?
     * AND KEYCOL=?
     * </PRE>
     * @return Return an item from the Map.
     */
    private String getRemoveStmt()
    {
        StringBuffer stmt = new StringBuffer();
        stmt.append("DELETE FROM ");
        stmt.append(mapTable.toString());
        stmt.append(" WHERE ");
        for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                stmt.append(" AND ");
            }
            stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }
        for (int i=0; i<keyMapping.getNumberOfDatastoreFields(); i++)
        {
            stmt.append(" AND ");
            stmt.append(keyMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)keyMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }
        return stmt.toString();
    }

    /**
     * Generate statement to clear the Map.
     * Deletes the links from the join table for this Map, leaving the value
     * objects in their own table(s).
     * <PRE>
     * DELETE FROM MAPTABLE
     * WHERE OWNERCOL=?
     * </PRE>
     * @return Statement to clear the Map.
     */
    private String getClearStmt()
    {
        StringBuffer stmt = new StringBuffer();
        stmt.append("DELETE FROM ");
        stmt.append(mapTable.toString());
        stmt.append(" WHERE ");
        for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                stmt.append(" AND ");
            }
            stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }
        return stmt.toString();
    }

    /**
     * Accessor for the higher id when elements primary key can't be part of
     * the primary key by datastore limitations like BLOB types can't be
     * primary keys.
     * @param sm State Manager for container
     * @return The next id
     */
    private int getNextIDForAdapterColumn(StateManager sm)
    {
        int nextID;
        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                String stmt = getMaxAdapterColumnIdStmt();
                PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmt);

                try
                {
                    int jdbcPosition = 1;
                    jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    ResultSet rs = sqlControl.executeStatementQuery(mconn, stmt, ps);
                    try
                    {
                        if (!rs.next())
                        {
                            nextID = 1;
                        }
                        else
                        {
                            nextID = rs.getInt(1)+1;
                        }

                        SQLWarnings.log(rs);
                    }
                    finally
                    {
                        rs.close();
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new JPOXDataStoreException(LOCALISER.msg("056020",getMaxAdapterColumnIdStmt()),e);
        }

        return nextID;
    }

    /**
     * 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 (SQLException e)
            {
                throw new JPOXDataStoreException(LOCALISER.msg("056016", putStmt), 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 (SQLException e)
            {
                throw new JPOXDataStoreException(LOCALISER.msg("056016", updateStmt), e);
            }
        }
    }

    /**
     * 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 (SQLException e)
            {
                throw new JPOXDataStoreException(LOCALISER.msg("056016", exists ? updateStmt : putStmt),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 process a "put" statement (where the key has no value in the join table).
     * @param ownerSM StateManager for the owner
     * @param conn The Connection
     * @param batched Whether we are batching it
     * @param key The key
     * @param value The value
     * @param executeNow Whether to execute the statement now or wait til batching
     * @return The return codes from any executed statement
     * @throws SQLException Thrown if an error occurs
     */
    protected int[] internalPut(StateManager ownerSM, ManagedConnection conn, boolean batched, Object key, Object value, boolean executeNow)
    throws SQLException
    {
        ObjectManager om = ownerSM.getObjectManager();
        SQLController sqlControl = storeMgr.getSQLController();
        PreparedStatement ps = sqlControl.getStatementForUpdate(conn, putStmt, false);
        try
        {
            int jdbcPosition = 1;
            if (valueMapping != null)
            {
                jdbcPosition = populateValueInStatement(om, ps, value, jdbcPosition);
            }
            else
            {
                jdbcPosition = populateEmbeddedValueFieldsInStatement(ownerSM, value, ps, jdbcPosition, (JoinTable)mapTable);
            }
            jdbcPosition = populateOwnerInStatement(ownerSM, om, ps, jdbcPosition);
            if (adapterMapping != null)
            {
                // Only set the adapter mapping if we have a new object
                long nextIDAdapter = getNextIDForAdapterColumn(ownerSM);
                adapterMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, adapterMapping), new Long(nextIDAdapter));
                jdbcPosition += adapterMapping.getNumberOfDatastoreFields();
            }
            jdbcPosition = populateKeyInStatement(om, ps, key, jdbcPosition);

            // Execute the statement
            return sqlControl.executeStatementUpdate(conn, putStmt, ps, true);
        }
        finally
        {
            sqlControl.closeStatement(conn, ps);
        }
    }

    /**
     * Method to process an "update" statement (where the key already has a value in the join table).
     * @param ownerSM StateManager for the owner
     * @param conn The Connection
     * @param batched Whether we are batching it
     * @param key The key
     * @param value The new value
     * @param executeNow Whether to execute the statement now or wait til any batch
     * @throws SQLException Thrown if an error occurs
     */
    protected void internalUpdate(StateManager ownerSM, ManagedConnection conn, boolean batched, Object key, Object value, boolean executeNow)
    throws SQLException
    {
        ObjectManager om = ownerSM.getObjectManager();
        SQLController sqlControl = storeMgr.getSQLController();
        PreparedStatement ps = sqlControl.getStatementForUpdate(conn, updateStmt, false);
        try
        {
            int jdbcPosition = 1;
            if (valueMapping != null)
            {
                jdbcPosition = populateValueInStatement(om, ps, value, jdbcPosition);
            }
            else
            {
                jdbcPosition = populateEmbeddedValueFieldsInStatement(ownerSM, value, ps, jdbcPosition, (JoinTable)mapTable);
            }
            jdbcPosition = populateOwnerInStatement(ownerSM, om, ps, jdbcPosition);
            jdbcPosition = populateKeyInStatement(om, ps, key, jdbcPosition);

            if (batched)
            {
                ps.addBatch();
            }
            else
            {
                sqlControl.executeStatementUpdate(conn, updateStmt, ps, true);
            }
        }
        finally
        {
            sqlControl.closeStatement(conn, ps);
        }
    }

    /**
     * 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)
        {
            try
            {
                ManagedConnection mconn = storeMgr.getConnection(om);
                SQLController sqlControl = storeMgr.getSQLController();
                try
                {
                    PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, removeStmt, false);
                    try
                    {
                        int jdbcPosition = 1;
                        jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                        jdbcPosition = populateKeyInStatement(om, ps, key, jdbcPosition);
                        sqlControl.executeStatementUpdate(mconn, removeStmt, ps, true);
                    }
                    finally
                    {
                        sqlControl.closeStatement(mconn, ps);
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (SQLException e)
            {
                throw new JPOXDataStoreException(LOCALISER.msg("056012",removeStmt),e);
            }
        }

        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;
    }

    /**
     * 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());
                }
            }
        }

        try
        {
            ObjectManager om = ownerSM.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, clearStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    jdbcPosition = populateOwnerInStatement(ownerSM, om, ps, jdbcPosition);
                    sqlControl.executeStatementUpdate(mconn, clearStmt, ps, true);
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new JPOXDataStoreException(LOCALISER.msg("056013",clearStmt),e);
        }

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

    /**
     * Accessor for the keys in the Map.
     * @param clr The ClassLoaderResolver
     * @return The keys
     **/
    public synchronized SetStore keySetStore(ClassLoaderResolver clr)
    {
        if (keySetStore == null)
        {
            keySetStore = new MapKeySetStore((MapTable)mapTable, clr);
        }
        return keySetStore;
    }

    /**
     * Accessor for the values in the Map.
     * @param clr The ClassLoaderResolver
     * @return The values.
     **/
    public synchronized SetStore valueSetStore(ClassLoaderResolver clr)
    {
        if (valueSetStore == null)
        {
            valueSetStore = new MapValueSetStore((MapTable)mapTable, this, clr);
        }
        return valueSetStore;
    }

    /**
     * Accessor for the map entries in the Map.
     * @return The map entries.
     **/
    public synchronized SetStore entrySetStore()
    {
        if (entrySetStore == null)
        {
            entrySetStore = new MapEntrySetStore((MapTable)mapTable, this, clr);
        }
        return entrySetStore;
    }

    /**
     * Accessor for the Get statement to retrieve a value from the Map.
     * @param ownerSm The owner StateManager
     * @param key The search key
     * @return The QueryStatement.
     **/
    protected QueryExpression getGetStatement(StateManager ownerSm, Object key)
    {
        QueryExpression stmt = null;
        final ClassLoaderResolver clr=ownerSm.getObjectManager().getClassLoaderResolver();
        if (valuesAreEmbedded || valuesAreSerialised)
        {
            stmt = dba.newQueryStatement(mapTable, clr);
            stmt.select(valueMapping);
        }
        else
        {
            // Value = PC
            stmt = new UnionIteratorStatement(clr, clr.classForName(this.valueType), true, this.storeMgr,
                clr.classForName(valueType), valueMapping, mapTable, false, null,
                true, false).getQueryStatement(null);
        }

        // Apply condition on join-table owner field to filter by owner
        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
        ScalarExpression ownerVal = ownerMapping.newLiteral(stmt, ownerSm.getObject());
        stmt.andCondition(ownerExpr.eq(ownerVal), true);

        // TODO this is a database hack. :-)
        // if the keyMapping contains a BLOB column (or any other column not supported by the database
        // as primary key), uses like instead of the operator OP_EQ (=)
        // in future do not check if the keyMapping is of ObjectMapping, but use the database
        // adapter to check the data types not supported as primary key
        // if object mapping (BLOB) use like
        // TODO Cater for embedded key
        if (keyMapping instanceof SerialisedMapping)
        {
            // Apply condition on join-table owner field to filter by owner
            ScalarExpression keyExpr = keyMapping.newScalarExpression(stmt,stmt.getMainTableExpression());
            ScalarExpression keyVal = keyMapping.newLiteral(stmt, key).add(dba.getMapping(String.class, storeMgr).newLiteral(stmt,"%"));
            stmt.andCondition(new BooleanExpression(keyExpr,ScalarExpression.OP_LIKE,keyVal), true);            
        }
        else
        {
            // Apply condition on join-table owner field to filter by owner
            ScalarExpression keyExpr = keyMapping.newScalarExpression(stmt,stmt.getMainTableExpression());
            ScalarExpression keyVal = keyMapping.newLiteral(stmt, key);
            stmt.andCondition(keyExpr.eq(keyVal), true);
        }
        return stmt;
    }

    /**
     * Generate statement for obtaining the maximum id.
     * <PRE>
     * SELECT MAX(SCOID) FROM MAPTABLE
     * WHERE OWNERCOL=?
     * </PRE>
     * @return The Statement returning the higher id
     */
    private String getMaxAdapterColumnIdStmt()
    {
        StringBuffer stmt = new StringBuffer();
        stmt.append("SELECT MAX(" + adapterMapping.getDataStoreMapping(0).getDatastoreField().getIdentifier().toString() + ")");
        stmt.append(" FROM ");
        stmt.append(mapTable.toString());
        stmt.append(" WHERE ");
        for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                stmt.append(" AND ");
            }
            stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }
        return stmt.toString();
    }

    // ---------------------------- JDOQL Query Methods ------------------------------
    /**
     * Utility to return a new QueryStatement.
     * @param sm The state manager
     * @param candidateClass The base value class
     * @param candidateAlias Alias for the candidate
     * @return The QueryStatement
     **/
    public QueryExpression newQueryStatement(StateManager sm, String candidateClass,
            DatastoreIdentifier candidateAlias)
    {
        // TODO Use the candidateAlias instead of "mapTableAlias"
        if (valuesAreEmbedded || valuesAreSerialised)
        {
            throw new JPOXUserException(LOCALISER.msg("056022"));
        }
        if (!clr.isAssignableFrom(valueType,candidateClass))
        {
            throw new IncompatibleQueryElementTypeException(valueType, candidateClass);
        }

        ClassLoaderResolver clr = sm.getObjectManager().getClassLoaderResolver();
        DatastoreIdentifier mapTableAlias = storeMgr.getIdentifierFactory().newIdentifier(IdentifierFactory.TABLE, "map");
        QueryExpression stmt = dba.newQueryStatement(mapTable, mapTableAlias, clr);

        // Join to owner
        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
        ScalarExpression ownerVal = ownerMapping.newLiteral(stmt, sm.getObject());
        stmt.andCondition(ownerExpr.eq(ownerVal), true);

        if (storeMgr.getOMFContext().getTypeManager().isSupportedType(candidateClass))
        {
            // Non-PC(embedded) - select the join table value
            stmt.select(mapTableAlias, valueMapping);
        }
        else
        {
            // PC - Join the value table on the value ID column
            DatastoreClass candidateTable = storeMgr.getDatastoreClass(candidateClass, clr);

            // Add the element table to the query
            // TODO Why use the default table alias ?, and why the default table expression below?
            // This uses the assumption that if this is a subquery then we find the right outer query candidate!
            stmt.newTableExpression(candidateTable, stmt.getMainTableAlias());

            // Inner Join from the ID of the value to the value mapping of the join table
            JavaTypeMapping valueTableID = candidateTable.getIDMapping();
            ScalarExpression valueMapExpr = valueMapping.newScalarExpression(stmt,
                stmt.getTableExpression(mapTableAlias));
            ScalarExpression valueExpr = valueTableID.newScalarExpression(stmt,
                stmt.getMainTableExpression());
            stmt.innerJoin(valueExpr, valueMapExpr, stmt.getMainTableExpression(), true, true);

            // Select the ID of the value table
            stmt.select(valueTableID);
        }

        return stmt;
    }

    /**
     * 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.getOMFContext().getTypeManager().isSupportedType(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.getOMFContext().getTypeManager().isSupportedType(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.jpox.store.rdbms.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.