Package com.force.sdk.jpa

Source Code of com.force.sdk.jpa.ForcePersistenceHandler

/**
* Copyright (c) 2011, salesforce.com, inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
*    Redistributions of source code must retain the above copyright notice, this list of conditions and the
*    following disclaimer.
*
*    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*    the following disclaimer in the documentation and/or other materials provided with the distribution.
*
*    Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
*    promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

package com.force.sdk.jpa;

import static com.force.sdk.jpa.ForceEntityManager.LOGGER;

import java.util.*;

import org.datanucleus.ObjectManager;
import org.datanucleus.exceptions.*;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.state.ObjectProviderImpl;
import org.datanucleus.store.*;

import com.force.sdk.jpa.exception.ForceApiExceptionMap;
import com.force.sdk.jpa.table.TableImpl;
import com.sforce.soap.partner.*;
import com.sforce.soap.partner.Error;
import com.sforce.soap.partner.fault.ApiFault;
import com.sforce.soap.partner.sobject.SObject;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.bind.CalendarCodec;
import com.sforce.ws.bind.XmlObject;

/**
*
* Persistence handler that handles all CRUD operations and translates them into
* API calls.
*
* @author Fiaz Hossain
*/
public class ForcePersistenceHandler extends AbstractPersistenceHandler {

    protected final ForceStoreManager storeManager;

    /**
     * Creates the persistence handler that will be used for all API operations.
     *
     * @param storeManager the store manager
     */
    public ForcePersistenceHandler(StoreManager storeManager) {
        this.storeManager = (ForceStoreManager) storeManager;
    }

    @Override
    public void close() {
        // Nothing to close here
    }

    /**
     * Deletes a persistent object from the datastore.
     *
     * @param op The ObjectProvider of the object to be deleted.
     */
    @Override
    public void deleteObject(ObjectProvider op) {
        // Check if read-only so update not permitted
        storeManager.assertReadOnlyForUpdateOfObject(op);

        ForceManagedConnection mconn = (ForceManagedConnection) storeManager.getConnection(op.getExecutionContext());
        ObjectManager om = ((ObjectProviderImpl) op).getStateManager().getObjectManager();
        boolean isAllOrNothingMode = om instanceof ForceObjectManagerImpl && ((ForceObjectManagerImpl) om).isInAllOrNothingMode();
        try {
            Object pkValue = op.provideField(op.getClassMetaData().getPKMemberPositions()[0]);
            if (!isAllOrNothingMode) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Deleting object: " + pkValue);
                }
                DeleteResult[] results = ((PartnerConnection) mconn.getConnection()).delete(new String[]{(String) pkValue});
                checkForErrors(results);
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Queuing for A-O-N delete object: " + pkValue);
                }
                ((ForceObjectManagerImpl) om).addToDeleteList((String) pkValue);
            }
        } catch (ApiFault af) {
            throw ForceApiExceptionMap.mapToNucleusException(af, false /* isQuery */,
                    storeManager.isEnableOptimisticTransactions());
        } catch (NucleusOptimisticException noe) {
            throw noe;
        } catch (ConnectionException x) {
            throw new NucleusDataStoreException(x.getMessage(), x);
        } finally {
            mconn.release();
        }
    }

    /**
     * Fetches a persistent object from the database.
     *
     * @param op           The ObjectProvider of the object to be fetched.
     * @param fieldNumbers The numbers of the fields to be fetched.
     */
    @Override
    public void fetchObject(ObjectProvider op, int[] fieldNumbers) {
        ForceManagedConnection mconn = (ForceManagedConnection) storeManager.getConnection(op.getExecutionContext());
        try {
            int pkPosition = op.getClassMetaData().getPKMemberPositions()[0];
            /**
             * Check if we are being asked for PK only. If so, return the ID that has been passed in
             */
            ForceFetchFieldManager fm;
            if (fieldNumbers.length == 1 && fieldNumbers[0] == pkPosition) {
                XmlObject sObject = new XmlObject();
                sObject.addField("Id", op.provideField(pkPosition));
                fm = new ForceFetchFieldManager(op, storeManager, mconn, sObject, null);
            } else {
                fm = new ForceFetchFieldManager(op, storeManager, mconn,
                    op.provideField(pkPosition), fieldNumbers, null);
            }
            op.replaceFields(fieldNumbers, fm);
        } catch (ApiFault af) {
            throw ForceApiExceptionMap.mapToNucleusException(af, false /* isQuery */,
                    storeManager.isEnableOptimisticTransactions());
        } catch (NucleusObjectNotFoundException onf) {
            throw onf;
        } catch (Exception x) {
            throw new NucleusDataStoreException(x.getMessage(), x);
        } finally {
            mconn.release();
        }
    }

    @Override
    public Object findObject(ExecutionContext ectx, Object id) {
        // We are not an ODBMS so we do not provide any objects here
        return null;
    }

    /**
     * Inserts a persistent object into the database.
     *
     * @param op The ObjectProvider of the object to be inserted.
     * @throws NucleusDataStoreException when an error occurs in the datastore communication
     */
    @Override
    public void insertObject(ObjectProvider op) {
        upsert(op, null);
    }

    /**
     * Locates this object in the datastore.
     *
     * @param op The ObjectProvider for the object to be found
     */
    @Override
    public void locateObject(ObjectProvider op) {
        ForceManagedConnection mconn = (ForceManagedConnection) storeManager.getConnection(op.getExecutionContext());
        try {
            AbstractClassMetaData acmd = op.getClassMetaData();
            TableImpl table = storeManager.getTable(acmd);
            QueryResult qr = ((PartnerConnection) mconn.getConnection())
                .query("select count() from " + table.getTableName().getForceApiName() + " where id='"
                        + op.provideField(op.getClassMetaData().getPKMemberPositions()[0]) + "'");
            if (qr.getSize() == 0) {
                throw new NucleusObjectNotFoundException();
            }
        } catch (ConnectionException x) {
            throw new NucleusDataStoreException(x.getMessage(), x);
        } finally {
            mconn.release();
        }
    }

    /**
     * Updates a persistent object in the datastore.
     *
     * @param op           The ObjectProvider of the object to be updated.
     * @param fieldNumbers The numbers of the fields to be updated.
     */
    @Override
    public void updateObject(ObjectProvider op, int[] fieldNumbers) {
        upsert(op, fieldNumbers);
    }

    /**
     * Creates objects for AllOrNothing operations.
     *
     * @param objects the objects to be created
     * @param objectProviders the object providers for each object
     * @param ec the execution context for this transaction
     */
    public void createObjects(Collection<SObject> objects, Collection<ObjectProvider> objectProviders, ExecutionContext ec) {
        ForceManagedConnection mconn = (ForceManagedConnection) storeManager.getConnection(ec);
        try {
            SObject[] toSave = objects.toArray(new SObject[objects.size()]);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Creating objects: " + toString(toSave, false, null));
            }
            PartnerConnection connection = (PartnerConnection) mconn.getConnection();
            connection.setAllOrNoneHeader(true);
            try {
                //connection.getConfig().setTraceMessage(true);
                SaveResult[] results = connection.create(toSave);
                checkForErrors(results);
                int i = 0;
                for (ObjectProvider op : objectProviders) {
                    op.setPostStoreNewObjectId(results[i++].getId());
                }
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Created objects: " + toString(results, false, null));
                }
            } finally {
                connection.setAllOrNoneHeader(false);
            }
        } catch (NucleusOptimisticException noe) {
            throw noe;
        } catch (NucleusUserException nue) {
            throw nue;
        } catch (Exception x) {
            throw new NucleusDataStoreException(x.getMessage(), x);
        } finally {
            mconn.release();
        }
    }

    /**
     * Updates objects for AllOrNothing operations.
     *
     * @param objects  the objects to be updated
     * @param versions the versions corresponding with each object for if-modified-before checks for optimistic transactions
     * @param ec the execution context of this transaction
     */
    public void updateObjects(SObject[] objects, Calendar[] versions, ExecutionContext ec) {
        ForceManagedConnection mconn = (ForceManagedConnection) storeManager.getConnection(ec);
        try {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Updating objects: " + toString(objects, true, null));
            }
            PartnerConnection connection = getPartnerConnection(mconn, versions);
            connection.setAllOrNoneHeader(true);
            try {
                SaveResult[] results = connection.update(objects);
                checkForErrors(results);
            } finally {
                try {
                    connection.setAllOrNoneHeader(false);
                } finally {
                    connection.clearConditionalRequestHeader();
                }
            }
        } catch (ApiFault af) {
            throw ForceApiExceptionMap.mapToNucleusException(af, false /* isQuery */,
                    storeManager.isEnableOptimisticTransactions());
        } catch (NucleusOptimisticException noe) {
            throw noe;
        } catch (NucleusUserException nue) {
            throw nue;
        } catch (Exception x) {
            throw new NucleusDataStoreException(x.getMessage(), x);
        } finally {
            mconn.release();
        }
    }

    /**
     * Deletes objects for AllOrNothing operations.
     *
     * @param objects the objects to be deleted
     * @param ec the execution context for this transaction
     */
    public void deleteObjects(String[] objects, ExecutionContext ec) {
        ForceManagedConnection mconn = (ForceManagedConnection) storeManager.getConnection(ec);
        try {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Deleting objects: " + Arrays.toString(objects));
            }
            PartnerConnection connection = (PartnerConnection) mconn.getConnection();
            connection.setAllOrNoneHeader(true);
            try {
                DeleteResult[] results = connection.delete(objects);
                checkForErrors(results);
            } finally {
                connection.setAllOrNoneHeader(false);
            }
        } catch (ApiFault af) {
            throw ForceApiExceptionMap.mapToNucleusException(af, false /* isQuery */,
                    storeManager.isEnableOptimisticTransactions());
        } catch (NucleusOptimisticException noe) {
            throw noe;
        } catch (NucleusUserException nue) {
            throw nue;
        } catch (Exception x) {
            throw new NucleusDataStoreException(x.getMessage(), x);
        } finally {
            mconn.release();
        }
    }

    private void upsert(ObjectProvider op, int[] fieldNumbers) {
        if (op.getClassMetaData().isEmbeddedOnly()) {
            // Embedded entities will be saved by the parent
            return;
        }
        // Check if read-only so update not permitted
        storeManager.assertReadOnlyForUpdateOfObject(op);

        if (!storeManager.managesClass(op.getClassMetaData().getFullClassName())) {
            storeManager.addClass(op.getClassMetaData().getFullClassName(), op.getExecutionContext().getClassLoaderResolver());
        }

        ForceManagedConnection mconn = (ForceManagedConnection) storeManager.getConnection(op.getExecutionContext());
        try {
            ForceInsertFieldManager fm = new ForceInsertFieldManager(op, storeManager,
                    fieldNumbers != null ? op.provideField(op.getClassMetaData().getPKMemberPositions()[0]) : null);
            op.provideFields(fieldNumbers != null ? fieldNumbers : op.getClassMetaData().getAllMemberPositions(), fm);
            /**
             * It is possible that this field is just a parent whose children have been updated only. In that case
             * the current object will not be dirty and we have nothing else to do.
             */
            if (!fm.isDirty()) return;
            ObjectManager om = ((ObjectProviderImpl) op).getStateManager().getObjectManager();
            boolean isAllOrNothingMode =
                om instanceof ForceObjectManagerImpl && ((ForceObjectManagerImpl) om).isInAllOrNothingMode();
            SObject toSave;
            if (!isAllOrNothingMode) {
                PartnerConnection connection = getPartnerConnection(mconn, op);
                try {
                    toSave = fm.getSObject(false);
                    if (LOGGER.isDebugEnabled()) {
                        if (fieldNumbers != null) {
                            LOGGER.debug("Updating object: " + toSave.getType() + " id: " + toSave.getId());
                        } else {
                            LOGGER.debug("Creating object: " + toSave.getType());
                        }
                    }
                    SaveResult[] results = fieldNumbers != null ? connection.update(new SObject[]{toSave})
                                                : ((PartnerConnection) mconn.getConnection()).create(new SObject[]{toSave});
                    checkForErrors(results);
                    if (fieldNumbers == null) {
                        op.setPostStoreNewObjectId(results[0].getId());
                        if (LOGGER.isDebugEnabled()) {
                            LOGGER.debug("Created object id: " + results[0].getId());
                        }
                    }
                } finally {
                    connection.clearConditionalRequestHeader();
                }
            } else {
                if (fieldNumbers != null) {
                    //When we do all-or-nothing with optimistic transactions, per jpa spec we need to save properly
                    // even if some objects are missing @Version,
                    // so if op.getVersion is null, we give a Calendar set to System.currentTimeMilis + 1 HOUR so
                    // the if-modified-before check for the object without @Version will always succeed
                    toSave = fm.getSObject(false);
                    ((ForceObjectManagerImpl) om).addToUpdateList(toSave,
                            op.getVersion() != null ? (Calendar) op.getVersion() : getVersionForUnversioned());
                } else {
                    toSave = fm.getSObject(true);
                    ((ForceObjectManagerImpl) om).addToCreateList(toSave, op);
                }
                if (LOGGER.isDebugEnabled()) {
                    if (fieldNumbers != null) {
                        LOGGER.debug("Queuing for A-O-N update object: " + toSave.getType() + " id: " + toSave.getId());
                    } else {
                        LOGGER.debug("Queuing for A-O-N create object: " + toSave.getType());
                    }
                }
            }
        } catch (ApiFault af) {
            throw ForceApiExceptionMap.mapToNucleusException(af, false /* isQuery */,
                storeManager.isEnableOptimisticTransactions());
        } catch (NucleusException ne) {
            throw ne;
        } catch (Exception x) {
            throw new NucleusDataStoreException(x.getMessage(), x);
        } finally {
            mconn.release();
        }
    }

    private static Calendar getVersionForUnversioned() {
        long time = System.currentTimeMillis() + (60 * 60 * 1000);
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(time);
        return cal;
    }

    private PartnerConnection getPartnerConnection(ForceManagedConnection mconn, ObjectProvider op) {
        PartnerConnection connection = (PartnerConnection) mconn.getConnection();
        if (op.getVersion() != null && storeManager.isEnableOptimisticTransactions()) {
            ConditionalRequestHeader_element ch = new ConditionalRequestHeader_element();
            ch.setIfModifiedBefore((Calendar) op.getVersion());
            connection.__setConditionalRequestHeader(ch);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Conditional header set to: " + new CalendarCodec().getValueAsString(ch.getIfModifiedBefore()));
            }
        }
        return connection;
    }

    private PartnerConnection getPartnerConnection(ForceManagedConnection mconn, Calendar[] versions) {
        PartnerConnection connection = (PartnerConnection) mconn.getConnection();
        if (versions.length > 0 && storeManager.isEnableOptimisticTransactions()) {
            ConditionalRequestHeader_element ch = new ConditionalRequestHeader_element();
            ch.setIfModifiedBeforeArray(versions);
            connection.__setConditionalRequestHeader(ch);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Conditional header set to: " + toString(ch.getIfModifiedBeforeArray(), false, new CalendarCodec()));
            }
        }
        return connection;
    }

    private void checkForErrors(SaveResult[] results) {
        List<Error> failures = null;
        boolean optimisticFailure = false;
        for (SaveResult sr : results) {
            if (!sr.getSuccess()) {
                if (failures == null) {
                    failures = new ArrayList<Error>();
                }
                optimisticFailure = handleError(failures, sr.getErrors()[0]);
            }
        }
        handleFailures(failures, optimisticFailure);
    }

    private static boolean handleError(List<Error> failures, Error error) {
        failures.add(error);
        return error.getStatusCode() == StatusCode.ENTITY_FAILED_IFLASTMODIFIED_ON_UPDATE;
    }

    private static void handleFailures(List<Error> failures, boolean optimisticFailure) {
        if (failures != null) {
            Iterator<Error> iter = failures.iterator();
            while (iter.hasNext()) {
                Error error = iter.next();
                if (error.getStatusCode() == StatusCode.ALL_OR_NONE_OPERATION_ROLLED_BACK && failures.size() > 1) {
                    iter.remove();
                }
            }
            if (optimisticFailure) {
                throw new NucleusOptimisticException(failures.get(0).getMessage(),
                        failures.size() == 1 ? failures.get(0) : failures.toArray(new Error[failures.size()]));
            } else {
                throw new NucleusUserException(failures.get(0).getMessage(),
                        failures.size() == 1 ? failures.get(0) : failures.toArray(new Error[failures.size()]));
            }
        }
    }

    /**
     * Checks for errors in a {@code delete()} call and handles the results properly.
     *
     * @param results the results from the {@code delete()} API call
     */
    public static void checkForErrors(DeleteResult[] results) {
        List<Error> failures = null;
        boolean optimisticFailure = false;
        for (DeleteResult dr : results) {
            if (!dr.getSuccess()) {
                if (failures == null) {
                    failures = new ArrayList<Error>();
                }
                optimisticFailure = handleError(failures, dr.getErrors()[0]);
            }
        }
        handleFailures(failures, optimisticFailure);
    }

    /**
     * Checks for errors in an {@code emptyRecycleBin()} call.
     *
     * @param results the results from the {@code emptyRecycleBin()} API call
     */
    public static void checkForRecycleBinErrors(EmptyRecycleBinResult[] results) {
        List<Error> failures = null;
        boolean optimisticFailure = false;
        for (EmptyRecycleBinResult dr : results) {
            if (!dr.getSuccess()) {
                if (failures == null) {
                    failures = new ArrayList<Error>();
                }
                optimisticFailure = handleError(failures, dr.getErrors()[0]);
            }
        }
        handleFailures(failures, optimisticFailure);
    }
       
    private static String toString(Object[] objects, boolean isUpdate, CalendarCodec cCodec) {
        StringBuilder sb = new StringBuilder(objects.length * 40);
        sb.append("[");
        for (Object obj : objects) {
            if (sb.length() > 1) sb.append(", ");
            if (obj instanceof SObject) {
                SObject s = (SObject) obj;
                sb.append("entity: ").append(s.getType());
                if (isUpdate) {
                    sb.append(" id: ").append(s.getId());
                }
            } else if (obj instanceof SaveResult) {
                sb.append(((SaveResult) obj).getId());
            } else if (obj instanceof Calendar) {
                sb.append(cCodec.getValueAsString(obj));
            }
        }
        sb.append("]");
        return sb.toString();
    }
}
TOP

Related Classes of com.force.sdk.jpa.ForcePersistenceHandler

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.