Package com.force.sdk.jpa

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

/**
* 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 java.util.*;

import javax.jdo.PersistenceManagerFactory;
import javax.jdo.spi.PersistenceCapable;
import javax.persistence.*;

import org.datanucleus.PersistenceConfiguration;
import org.datanucleus.StateManager;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.jpa.*;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.QueryResultMetaData;
import org.datanucleus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.force.sdk.jpa.query.ForceQueryUtils;
import com.force.sdk.jpa.query.QueryHints;

/**
*
* The main EntityManager for the Force.com JPA implementation.  Persist,
* find, etc. calls go through here. This class is where we configure native queries
* to use SOQL.
*
* @author Fiaz Hossain
*/
public class ForceEntityManager extends EntityManagerImpl {
   
    private static final String SOQL_LANGUAGE = "SOQL";
    private static final Calendar EPOCH_TIME = new GregorianCalendar(1970, 0, 1, 0, 0, 0);
   
    /**
     * This is the logger for logging all JPA request.
     * The convention here is that all calls to JPA interface starts with JPA <operation_name>
     *
     * e.g. JPA Persist ...
     *
     * for a persist call. This is to distinguish other JPA related calls that are logged by the datasource implementation.
     *
     */
    static final Logger LOGGER = LoggerFactory.getLogger("com.force.sdk.jpa");

    /**
     * Construct an entity manager for persisting objects to Force.com.
     *
     * @param emf the entity manager factory creating this entity manager
     * @param pmf the persistence manager factory that will create the persistence manager
     * @param contextType the persistence context type for this application
     */
    public ForceEntityManager(EntityManagerFactory emf, PersistenceManagerFactory pmf, PersistenceContextType contextType) {
        super(emf, pmf, contextType);
        if (tx != null) {
            tx = new ForceEntityTransactionImpl(om);
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("EntityManager created: " + this);
        }
    }
   
    /**
     * Overrides the base class method to implement our own ForceTransactionImpl.
     */
    @Override
    public void joinTransaction() {
        assertIsOpen();
        //assertIsActive();
        //TODO assertNotActive
        //assertTransactionNotRequired();
        tx = new ForceEntityTransactionImpl(om);
    }
   
    @Override
    public void close() {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("EntityManager close: " + this);
        }
        super.close();
    }

    @Override
    public void clear() {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("EntityManager clear: " + this);
        }
        super.clear();
    }

    /**
     * Method added to allow a StateManager to be injected into a transient object
     * so that we can track which fields have been touched. Then if the user calls
     * <code>merge()</code> on that transient object we can copy the fields over to a detached
     * object and call <code>merge()</code> on it. If the user called persist instead, we simply
     * discard this injected StateManager and proceed as usual.
     *
     * @param pc PersistenceCapable
     */
    public void injectStateManagerIntoTransient(PersistenceCapable pc) {
        AbstractClassMetaData acmd = om.getMetaDataManager().getMetaDataForClass(pc.getClass(), om.getClassLoaderResolver());
        ForceJPAStateManagerImpl sm = new ForceJPAStateManagerImpl(om, acmd);
        sm.initialiseForHollowPreConstructed(null, pc);
        om.putObjectIntoCache(sm);
        if (acmd.hasVersionStrategy()) {
            // This is not the right value but we need something to pacify DataNucleus.
            // We require that the user set a valid version before calling merge
            sm.setVersion(EPOCH_TIME);
        }
    }
   
    /**
     * Create an instance of Query for executing an SOQL query.
     * @param soqlString a native SOQL query string
     * @param resultClass the class of the resulting instance(s)
     * @return the new query instance
     */
    @Override
    public Query createNativeQuery(String soqlString, Class resultClass) {
        assertIsOpen();
        try {
            org.datanucleus.store.query.Query internalQuery = om.getOMFContext().getQueryManager().newQuery(
                    SOQL_LANGUAGE, om.getExecutionContext(), soqlString);
            if (resultClass != null) {
                internalQuery.setResultClass(resultClass);
            }
            return new JPAQuery(this, internalQuery, SOQL_LANGUAGE);
        } catch (NucleusException ne) {
            throw new IllegalArgumentException(ne.getMessage(), ne);
        }
    }
   
    /**
     * Create an instance of Query for executing an SOQL query.
     * @param soqlString a native SOQL query string
     * @param resultSetMapping the name of the result set mapping
     * @return the new query instance
     */
    @Override
    public Query createNativeQuery(String soqlString, String resultSetMapping) {
        assertIsOpen();
        try {
            org.datanucleus.store.query.Query internalQuery = om.getOMFContext().getQueryManager().newQuery(
                    SOQL_LANGUAGE, om.getExecutionContext(), soqlString);
            QueryResultMetaData qrmd = om.getMetaDataManager().getMetaDataForQueryResult(resultSetMapping);
            if (qrmd == null) {
                // TODO Localise this, and check if it is the correct exception to throw
                throw new IllegalArgumentException("ResultSetMapping " + resultSetMapping + " is not found");
            }
            internalQuery.setResultMetaData(qrmd);
            return new JPAQuery(this, internalQuery, SOQL_LANGUAGE);
        } catch (NucleusException ne) {
            throw new IllegalArgumentException(ne.getMessage(), ne);
        }
    }
   
    @Override
    public void persist(Object entity) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA Persist - entity: " + entity);
        }
        if (entity instanceof PersistenceCapable) {
            PersistenceCapable pc = (PersistenceCapable) entity;
            if (pc.jdoIsDetached()) {
                /**
                 * Sec - 3.2.2
                 * If X is a detached object, the EntityExistsException may be thrown when the persist
                 * operation is invoked, or the EntityExistsException or another PersistenceException may be thrown
                 * at flush or commit time
                 */
                throwException(new EntityExistsException("Entity already exists. Use merge to save changes."));
            }
            // We may have added a state manager which is not needed for persist calls
            StateManager sm = om.findStateManager(entity);
            if (sm != null) {
                if (sm instanceof ForceJPAStateManagerImpl) {
                    om.clearDirty(sm);
                    om.removeStateManager(sm);
                    pc.jdoReplaceStateManager(null);
                } else {
                    /**
                     * Sec - 3.2.2
                     * If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist
                     * operation is cascaded to entities referenced by X, if the relationships from X to these other
                     * entities are annotated with the cascade=PERSIST or cascade=ALL annotation element
                     * value or specified with the equivalent XML descriptor element.
                     */
                    // Do nothing and just return
                    if (LOGGER.isTraceEnabled()) {
                        LOGGER.trace("JPA Persist - entity already persisted doing nothing for entity: " + entity);
                    }
                    return;
                }
            }
        }
        super.persist(entity);
    }
   
    @Override
    public Object merge(Object entity) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA Merge: - entity: " + entity);
        }
        if (entity instanceof PersistenceCapable) {
            PersistenceCapable pc = (PersistenceCapable) entity;
            if (pc.jdoIsDetached() && pc.jdoGetObjectId() == null) {
                throw new IllegalArgumentException("Detached entity with null id cannot be merged.");
            }
            // Read id from entity
            StateManager sm = om.findStateManager(entity);
            if (sm != null) {
                // This path is taken by all merge() calls.
                // We need to isolate merge() calls that were made to Transient object instead of Detached objects
                AbstractClassMetaData acmd = om.getMetaDataManager()
                                                    .getMetaDataForClass(entity.getClass(), om.getClassLoaderResolver());
                Object id = ForceQueryUtils.getIdFromObject(pc, acmd);
                if (id != null) {
                    if (sm instanceof ForceJPAStateManagerImpl) {
                            sm.initialiseForDetached(pc, id, sm.getVersion(pc));
                            entity = sm.getObject();
                    }
                }
            }
        }
        return super.merge(entity);
    }
   
    @Override
    public void remove(Object entity) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA Remove - entity: " + entity);
        }
       
        try {
            super.remove(entity);
        } catch (IllegalArgumentException e) {
            // Add more descriptive message when this exception occurs because of
            // a detached entity
            if (e.getMessage().contains(LOCALISER.msg("EM.EntityIsDetached",
                    StringUtils.toJVMIDString(entity), "" + om.getApiAdapter().getIdForObject(entity)))) {
                throw new IllegalArgumentException(e.getMessage()
                        + " - It has most likely become detached since the find() operation."
                        + " Either put find() and remove() under same transaction or merge() the object first");
            } else {
                throw e;
            }
        }
    }
   
    @Override
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lock, Map<String, Object> properties) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA Find - entity: " + entityClass.getName() + " id: " + primaryKey + " lock: " + lock);
        }
        /**
         * TODO - Current DN implementation ignores the properties so we try to to do the best we can now. Once
         * there is full support for properties QueryHints can move elsewhere.
         */
        Object fetchDepth = properties != null ? properties.get(QueryHints.MAX_FETCH_DEPTH) : null;
        if (fetchDepth != null && !(fetchDepth instanceof Integer))
            throw new IllegalArgumentException(QueryHints.MAX_FETCH_DEPTH + " requires Integer value");
        Integer oldFetchDepth = null;
        if (fetchDepth != null) {
            oldFetchDepth = om.getFetchPlan().getMaxFetchDepth();
            om.getFetchPlan().setMaxFetchDepth((Integer) fetchDepth);
        }
        try {
            return super.find(entityClass, primaryKey, lock, properties);
        } catch (NucleusException ne) {
            throw NucleusJPAHelper.getJPAExceptionForNucleusException(ne);
        } finally {
            if (oldFetchDepth != null) {
                om.getFetchPlan().setMaxFetchDepth(oldFetchDepth);
            }
        }
    }
   
    @Override
    public Object getReference(Class entityClass, Object primaryKey) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA getReference - entity: " + entityClass.getName() + " id: " + primaryKey);
        }
        return super.getReference(entityClass, primaryKey);
    }
   

    @Override
    public void refresh(Object entity, LockModeType lock, Map<String, Object> properties) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA Refresh - entity: " + entity + " lock: " + lock);
        }
        super.refresh(entity, lock, properties);
    }
   
    /**
     * We ignore <code>flush()</code> if we are running within a transaction.
     * Use commit() instead for transactions.
     */
    @Override
    public void flush() {
        if (getTransaction().isActive()) {
            // We ignore flush if we are running within a transaction
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("JPA Flush ignored");
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("JPA Flush");
            }
            super.flush();
        }
    }
   

    @Override
    public void lock(Object entity, LockModeType lock, Map<String, Object> properties) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA Lock - entity: " + entity + " lock: " + lock);
        }
        super.lock(entity, lock, properties);
    }
   

    @Override
    public void detach(Object entity) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("JPA Detach - entity: " + entity);
        }
        super.detach(entity);
    }
   
    private void assertIsOpen() {
        if (om.isClosed()) {
            throw new IllegalStateException(LOCALISER.msg("EM.IsClosed"));
        }
    }
   
    /**
     * Convenience method to throw the supplied exception.
     * If the supplied exception is a PersistenceException, also mark the current transaction for rollback.
     * @param re The exception
     */
    private Object throwException(RuntimeException re) {
        if (re instanceof PersistenceException) {
            PersistenceConfiguration conf = om.getOMFContext().getPersistenceConfiguration();
            boolean markForRollback = conf.getBooleanProperty("datanucleus.jpa.txnMarkForRollbackOnException");
            if (markForRollback) {
                // The JPA spec says that all PersistenceExceptions thrown should mark the transaction for
                // rollback. Seems excessive to me. For example, you try to find an object with a particular id and it
                // doesn't exist so you then have to rollback the txn and start again.
                getTransaction().setRollbackOnly();
            }
        }
        throw re;
    }
}
TOP

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

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.