Package org.jpox.store.rdbms.request

Source Code of org.jpox.store.rdbms.request.DeleteRequest$DeleteMappingConsumer

/**********************************************************************
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 - conversion to use Logger
2005 Andy Jefferson - added handling for updating FK in related object
2006 Andy Jefferson - changed to extend VersionCheckRequest
    ...
**********************************************************************/
package org.jpox.store.rdbms.request;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.jpox.ClassLoaderResolver;
import org.jpox.ManagedConnection;
import org.jpox.ObjectManager;
import org.jpox.StateManager;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXOptimisticException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.IdentityType;
import org.jpox.metadata.InterfaceMetaData;
import org.jpox.metadata.Relation;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreField;
import org.jpox.store.mapped.StatementExpressionIndex;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.MappingCallbacks;
import org.jpox.store.mapped.mapping.MappingConsumer;
import org.jpox.store.mapped.mapping.Mappings;
import org.jpox.store.mapped.mapping.PersistenceCapableMapping;
import org.jpox.store.mapped.mapping.ReferenceMapping;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.store.rdbms.SQLController;
import org.jpox.store.rdbms.fieldmanager.ParameterSetter;
import org.jpox.store.rdbms.mapping.RDBMSMapping;
import org.jpox.util.JPOXLogger;
import org.jpox.util.StringUtils;

/**
* Class to provide a means of deletion of records from a data store.
* Extends basic request class implementing the execute method to do a JDBC delete operation.
* Provides a version check for optimistic handling.
*
* @version $Revision: 1.75 $
*/
public class DeleteRequest extends VersionCheckRequest
{
    private final MappingCallbacks[] callbacks;

    /** Statement for deleting the object from the datastore. */
    private final String deleteStmt;

    /** Statement for deleting the object from the datastore (optimistic txns). */
    private final String deleteStmtOptimistic;

    /** the index for the expression(s) in the delete statement. */
    private MappingStatementIndex mappingStatementIndex;

    /** PK fields to be provided in defining the record to be deleted (app identity cases). */
    private final int[] pkFieldNumbers;

    /** 1-1 bidir non-owner fields that are reachable (but not updated) and have no datastore column. */
    private final AbstractMemberMetaData[] oneToOneNonOwnerFields;

    /**
     * Constructor, taking the table. Uses the structure of the datastore table to build a basic query.
     * @param table The Class Table representing the datastore table to delete.
     * @param cls Class of objects being deleted
     * @param clr ClassLoader resolver
     */
    public DeleteRequest(DatastoreClass table, Class cls, ClassLoaderResolver clr)
    {
        super(table, cls, clr);

        mappingStatementIndex = new MappingStatementIndex(); // Populated using the subsequent lines
        DeleteMappingConsumer consumer = new DeleteMappingConsumer(clr, cmd);
        table.provideNonPrimaryKeyMappings(consumer); // to compute callbacks

        // WHERE clause - add identity
        consumer.setWhereClauseConsumption(true);
        table.providePrimaryKeyMappings(consumer);
        table.provideDatastoreIdMappings(consumer);

        // Basic delete statement
        deleteStmt = consumer.getStatement();

        // Add on the optimistic discriminator (if appropriate) to get the delete statement for optimistic txns
        if (versionMetaData != null)
        {
            if (versionMetaData.getFieldName() != null)
            {
                // Version field
                AbstractMemberMetaData[] versionFmds = new AbstractMemberMetaData[1];
                versionFmds[0] = cmd.getMetaDataForMember(versionMetaData.getFieldName());
                table.provideMappingsForFields(consumer, versionFmds, false);
            }
            else
            {
                // Surrogate version column
                table.provideVersionMappings(consumer);
            }
        }

        // Optimistic delete statement
        deleteStmtOptimistic = consumer.getStatement();

        pkFieldNumbers = consumer.getPrimaryKeyFieldNumbers();
        callbacks = (MappingCallbacks[])consumer.getMappingCallBacks().toArray(new MappingCallbacks[consumer.getMappingCallBacks().size()]);
        oneToOneNonOwnerFields = consumer.getOneToOneNonOwnerFields();
    }

    /**
     * Method performing the deletion of the record from the datastore.
     * Takes the constructed deletion query and populates with the specific record information.
     * @param sm The state manager for the record to be deleted.
     */
    public void execute(StateManager sm)
    {
        if (JPOXLogger.PERSISTENCE.isDebugEnabled())
        {
            // Debug information about what we are deleting
            JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("052210", StringUtils.toJVMIDString(sm.getObject()), table));
        }

        // Process all related fields first
        // a). Delete any dependent objects
        // b). Null any non-dependent objects with FK at other side
        for (int i = 0; i < callbacks.length; ++i)
        {
            if (JPOXLogger.PERSISTENCE.isDebugEnabled())
            {
                JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("052212",
                    StringUtils.toJVMIDString(sm.getObject()),
                    ((JavaTypeMapping)callbacks[i]).getFieldMetaData().getFullFieldName()));
            }
            callbacks[i].preDelete(sm);
        }

        if (oneToOneNonOwnerFields != null && oneToOneNonOwnerFields.length > 0)
        {
            for (int i=0;i<oneToOneNonOwnerFields.length;i++)
            {
                AbstractMemberMetaData relatedFmd = oneToOneNonOwnerFields[i];
                updateOneToOneBidirectionalOwnerObjectForField(sm, relatedFmd);
            }
        }

        // Choose the statement based on whether optimistic or not
        String stmt = null;
        ObjectManager om = sm.getObjectManager();
        RDBMSManager storeMgr = (RDBMSManager)om.getStoreManager();
        boolean optimisticChecks = (versionMetaData != null && om.getTransaction().getOptimistic() && versionChecks);
        if (optimisticChecks)
        {
            stmt = deleteStmtOptimistic;
        }
        else
        {
            stmt = deleteStmt;
        }

        // Process the delete of this object
        try
        {
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();

            try
            {
                // Perform the delete
                boolean batch = true;
                if (optimisticChecks || !om.getTransaction().isActive())
                {
                    // Turn OFF batching if doing optimistic checks (since we need the result of the delete)
                    // or if using nontransactional writes (since we want it sending to the datastore now)
                    batch = false;
                }
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, stmt, batch);
                try
                {
                    // provide primary key field(s)
                    if (cmd.getIdentityType() == IdentityType.DATASTORE)
                    {
                        table.getDataStoreObjectIdMapping().setObject(om, ps,
                            mappingStatementIndex.getDatastoreId().getParameterIndex(), sm.getInternalObjectId());
                    }
                    else if (cmd.getIdentityType() == IdentityType.APPLICATION)
                    {
                        sm.provideFields(pkFieldNumbers, new ParameterSetter(sm, ps,
                            mappingStatementIndex.getPrimaryKeys(),true));
                    }

                    if (optimisticChecks)
                    {
                        // WHERE clause - current version discriminator
                        JavaTypeMapping verMapping = mappingStatementIndex.getVersion2().getMapping();
                        Object currentVersion = sm.getTransactionalVersion(sm.getObject());
                        if (currentVersion == null)
                        {
                            // Somehow the version is not set on this object (not read in ?) so report the bug
                            String msg = LOCALISER.msg("052202",
                                sm.getInternalObjectId(), table);
                            JPOXLogger.PERSISTENCE.error(msg);
                            throw new JPOXException(msg);
                        }
                        verMapping.setObject(om, ps,
                            mappingStatementIndex.getVersion2().getParameterIndex(), currentVersion);
                    }

                    int[] rcs = sqlControl.executeStatementUpdate(mconn, stmt, ps, !batch);
                    if (optimisticChecks && rcs[0] == 0)
                    {
                        // No object deleted so either object disappeared or failed optimistic version checks
                        String msg = LOCALISER.msg("052203",
                            StringUtils.toJVMIDString(sm.getObject()), sm.getInternalObjectId(),
                            "" + sm.getTransactionalVersion(sm.getObject()));
                        JPOXLogger.DATASTORE.error(msg);
                        throw new JPOXOptimisticException(msg, sm.getObject());
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            String msg = LOCALISER.msg("052211",
                StringUtils.toJVMIDString(sm.getObject()), stmt, e.getMessage());
            JPOXLogger.DATASTORE_PERSIST.warn(msg);
            List exceptions = new ArrayList();
            exceptions.add(e);
            while((e = e.getNextException())!=null)
            {
                exceptions.add(e);
            }
            throw new JPOXDataStoreException(msg, (Throwable[])exceptions.toArray(new Throwable[exceptions.size()]));
        }
    }

    /**
     * Method to update any 1-1 bidir non-owner fields where the foreign-key is stored in the other object.
     * TODO DO this via StoreManager.update() call
     * @param sm StateManager of this object
     * @param fmd MetaData for field that has related (owner) objects
     */
    private void updateOneToOneBidirectionalOwnerObjectForField(StateManager sm, AbstractMemberMetaData fmd)
    {
        if (JPOXLogger.PERSISTENCE.isDebugEnabled())
        {
            JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("052217",
                StringUtils.toJVMIDString(sm.getObject()), fmd.getFullFieldName()));
        }

        RDBMSManager storeMgr = (RDBMSManager)sm.getStoreManager();
        ObjectManager om = sm.getObjectManager();
        ClassLoaderResolver clr = om.getClassLoaderResolver();
        AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
        // TODO Cater for more than 1 related field
        String fullClassName = ((AbstractClassMetaData)relatedMmds[0].getParent()).getFullClassName();

        //TODO I'm not sure that we need to loop all implementations. will we have the fk set to all tables, if many?
        String[] classes;
        if (((AbstractClassMetaData)relatedMmds[0].getParent()) instanceof InterfaceMetaData)
        {
            classes = storeMgr.getOMFContext().getMetaDataManager().getClassesImplementingInterface(fullClassName, clr);
        }
        else
        {
            classes = new String[] {fullClassName};
        }
        Set datastoreClasses = new HashSet();
        for (int i=0; i<classes.length; i++)
        {
            // just remove duplicates
            datastoreClasses.add(storeMgr.getDatastoreClass(classes[i], clr));
        }

        Iterator it = datastoreClasses.iterator();
        while (it.hasNext())
        {
            DatastoreClass refTable = (DatastoreClass)it.next();
            JavaTypeMapping refMapping = refTable.getFieldMapping(fmd.getMappedBy());
            if (refMapping.isNullable()) // Only clear the references that can be cleared
            {
                // Create a statement to clear the link from the previous related object
                StringBuffer clearLinkStmt = new StringBuffer("UPDATE " + refTable.toString() + " SET ");
                for (int j=0;j<refMapping.getNumberOfDatastoreFields();j++)
                {
                    if (j > 0)
                    {
                        clearLinkStmt.append(",");
                    }
                    clearLinkStmt.append(refMapping.getDataStoreMapping(j).getDatastoreField().getIdentifier());
                    clearLinkStmt.append("=NULL");
                }
                clearLinkStmt.append(" WHERE ");
                for (int j=0;j<refMapping.getNumberOfDatastoreFields();j++)
                {
                    if (j > 0)
                    {
                        clearLinkStmt.append(" AND ");
                    }
                    clearLinkStmt.append(refMapping.getDataStoreMapping(j).getDatastoreField().getIdentifier());
                    clearLinkStmt.append("=?");
                }

                try
                {
                    ManagedConnection mconn = storeMgr.getConnection(om);
                    SQLController sqlControl = storeMgr.getSQLController();
                    try
                    {
                        // Null out the relationship to the object being deleted.
                        PreparedStatement ps = null;
                        try
                        {
                            ps = sqlControl.getStatementForUpdate(mconn, clearLinkStmt.toString(), false);
                            refMapping.setObject(om, ps, Mappings.getParametersIndex(1, refMapping), sm.getObject());

                            sqlControl.executeStatementUpdate(mconn, clearLinkStmt.toString(), ps, true);
                        }
                        finally
                        {
                            if (ps != null)
                            {
                                sqlControl.closeStatement(mconn, ps);
                            }
                        }
                    }
                    finally
                    {
                        mconn.release();
                    }
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                    throw new JPOXDataStoreException("Update request failed", e);
                }
            }
        }
    }

    /**
     * Mapping Consumer used for generating the DELETE statement for an object in a table.
     * This statement will be of the form
     * <PRE>
     * DELETE FROM table-name WHERE id1=? AND id2=?
     * </PRE>
     * or (when also performing version checks)
     * <PRE>
     * DELETE FROM table-name WHERE id1=? AND id2=? AND version={oldvers}
     * </PRE>
     *
     * @version $Revision: 1.75 $
     */
    private class DeleteMappingConsumer implements MappingConsumer
    {
        /** Flag for initialisation state of the consumer. */
        boolean initialized = false;

        /** Where clause for the statement. Built during the consumption process. */
        StringBuffer where = new StringBuffer();

        /** Current parameter index. */
        int paramIndex = 1;

        /** Primary Key field numbers to use in identifying the record to delete (when app id). */
        private List pkFields = new ArrayList();

        /** Fields in a 1-1 relation with FK in the table of the other object. */
        private List oneToOneNonOwnerFields = new ArrayList();

        /** Mapping Callbacks to invoke at deletion. */
        private List mc = new ArrayList();

        /** ClassLoaderResolver **/
        private final ClassLoaderResolver clr;

        /** MetaData for the class of the object */
        private final AbstractClassMetaData cmd;

        private boolean whereClauseConsumption = false;

        /**
         * Constructor.
         * @param clr the ClassLoaderResolver
         * @param cmd AbstractClassMetaData
         */
        public DeleteMappingConsumer(ClassLoaderResolver clr, AbstractClassMetaData cmd)
        {
            this.clr = clr;
            this.cmd = cmd;
            this.paramIndex = 1;
        }

        public void setWhereClauseConsumption(boolean whereClause)
        {
            this.whereClauseConsumption = whereClause;
        }

        public void preConsumeMapping(int highest)
        {
            if (!initialized)
            {
                mappingStatementIndex.setPrimaryKeys(new StatementExpressionIndex[highest]);
                mappingStatementIndex.setFields(new StatementExpressionIndex[highest]);
                initialized = true;
            }
        }

        public void consumeMapping(JavaTypeMapping m, AbstractMemberMetaData fmd)
        {
            if (!fmd.getAbstractClassMetaData().isSameOrAncestorOf(cmd))
            {
                return;
            }
            if (m.includeInUpdateStatement())
            {
                if (fmd.isPrimaryKey())
                {
                    Integer abs_field_num = new Integer(fmd.getAbsoluteFieldNumber());
                    int parametersIndex[] = new int[m.getNumberOfDatastoreFields()];
                    StatementExpressionIndex sei = new StatementExpressionIndex();
                    sei.setMapping(m);
                    sei.setParameterIndex(parametersIndex);

                    mappingStatementIndex.getPrimaryKeys()[fmd.getAbsoluteFieldNumber()] = sei;
                    for (int j=0; j<parametersIndex.length; j++)
                    {
                        if (where.length() > 0)
                        {
                            where.append(" AND ");
                        }
                        String condition = m.getDataStoreMapping(j).getDatastoreField().getIdentifier() +
                            "=" + ((RDBMSMapping)m.getDataStoreMapping(j)).getUpdateInputParameter();
                        where.append(condition);

                        if (!pkFields.contains(abs_field_num))
                        {
                            pkFields.add(abs_field_num);
                        }
                        parametersIndex[j] = paramIndex++;
                    }
                }
                else if (m instanceof PersistenceCapableMapping || m instanceof ReferenceMapping)
                {
                    if (m.getNumberOfDatastoreFields() == 0)
                    {
                        // Field storing a PC object with FK at other side
                        int relationType = fmd.getRelationType(clr);
                        if (relationType == Relation.ONE_TO_ONE_BI)
                        {
                            if (fmd.getMappedBy() != null)
                            {
                                // 1-1 bidirectional field without datastore column(s) (with single FK at other side)
                                oneToOneNonOwnerFields.add(fmd);
                            }
                        }
                        else if (relationType == Relation.MANY_TO_ONE_BI)
                        {
                            AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
                            if (fmd.getJoinMetaData() != null || relatedMmds[0].getJoinMetaData() != null)
                            {
                                // 1-N bidirectional using join table for relation
                                // TODO Anything to do here ?
                            }
                        }
                    }
                }
                else
                {
                    if (whereClauseConsumption)
                    {
                        // Must be version field since nothing else should come through here
                        int parametersIndex[] = new int[m.getNumberOfDatastoreFields()];
                        parametersIndex[0] = paramIndex++;
                        StatementExpressionIndex sei = new StatementExpressionIndex();
                        sei.setMapping(m);
                        sei.setParameterIndex(parametersIndex);
                        mappingStatementIndex.setVersion2(sei);
                        String inputParam = ((RDBMSMapping)m.getDataStoreMapping(0)).getUpdateInputParameter();
                        String condition = " AND " + m.getDataStoreMapping(0).getDatastoreField().getIdentifier() + "=" + inputParam;
                        where.append(condition);
                    }
                }
            }

            // Build up list of mappings callbacks for the fields of this class.
            // The Mapping callback called delete is the preDelete
            if (m instanceof MappingCallbacks)
            {
                mc.add(m);
            }
        }

        /**
         * Consumes a mapping for a special column (version, datastore identity etc)
         * @param m The mapping
         * @param mappingType the Mapping type
         */
        public void consumeMapping(JavaTypeMapping m, int mappingType)
        {
            if (mappingType == MappingConsumer.MAPPING_TYPE_DATASTORE_ID)
            {
                where.append(m.getDataStoreMapping(0).getDatastoreField().getIdentifier().toString());
                where.append("=");
                where.append(((RDBMSMapping)m.getDataStoreMapping(0)).getUpdateInputParameter());

                int[] param = { paramIndex++ };
                mappingStatementIndex.getDatastoreId().setParameterIndex(param);
            }
            else if (mappingType == MappingConsumer.MAPPING_TYPE_VERSION)
            {
                if (whereClauseConsumption)
                {
                    StatementExpressionIndex versStmtIdx = mappingStatementIndex.getVersion2();
                    int[] param = { paramIndex++ };
                    versStmtIdx.setMapping(m);
                    versStmtIdx.setParameterIndex(param);
                    String inputParam = ((RDBMSMapping)m.getDataStoreMapping(0)).getUpdateInputParameter();
                    String condition = " AND " + m.getDataStoreMapping(0).getDatastoreField().getIdentifier() + "=" + inputParam;
                    where.append(condition);
                }
            }
        }

        /**
         * Consumer a datastore field without mapping.
         * @param fld The datastore field
         */
        public void consumeUnmappedDatastoreField(DatastoreField fld)
        {
            // Do nothing since we dont handle unmapped columns
        }

        /**
         * Accessor for the field numbers of any primary-key fields (application identity).
         * @return array of absolute primary key field numbers
         */
        public int[] getPrimaryKeyFieldNumbers()
        {
            int[] fieldNumbers = new int[pkFields.size()];
            for (int i = 0; i < pkFields.size(); i++)
            {
                fieldNumbers[i] = ((Integer)pkFields.get(i)).intValue();
            }
            return fieldNumbers;
        }

        /**
         * All 1-1 bidirectional non-owner fields, with the FK In the other object.
         * @return The fields that are 1-1 bidirectional with the FK at the other side.
         */
        public AbstractMemberMetaData[] getOneToOneNonOwnerFields()
        {
            AbstractMemberMetaData[] fmds = new AbstractMemberMetaData[oneToOneNonOwnerFields.size()];
            for (int i = 0; i < oneToOneNonOwnerFields.size(); ++i)
            {
                fmds[i] = (AbstractMemberMetaData) oneToOneNonOwnerFields.get(i);
            }
            return fmds;
        }

        /**
         * Obtain a List of mapping callbacks that will be run for this deletion.
         * @return the mapping callbacks
         */
        public List getMappingCallBacks()
        {
            return mc;
        }

        /**
         * Accessor for the delete SQL statement.
         * @return The delete SQL statement
         */
        public String getStatement()
        {
            return "DELETE FROM " + table.toString() + " WHERE " + where;
        }
    }
}
TOP

Related Classes of org.jpox.store.rdbms.request.DeleteRequest$DeleteMappingConsumer

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.