Package org.datanucleus.api.jpa

Source Code of org.datanucleus.api.jpa.JPAEntityManager

/**********************************************************************
Copyright (c) 2006 Erik Bengtson 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:
2006 Andy Jefferson - provded exception handling
2008 Andy Jefferson - change query interface to be independent of store.rdbms
2009 Andy Jefferson - add JPA2 methods
    ...
**********************************************************************/
package org.datanucleus.api.jpa;

import java.util.Map;
import java.util.Set;

import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContextType;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.TransactionRequiredException;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.Metamodel;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.NucleusContext;
import org.datanucleus.ObjectManager;
import org.datanucleus.ObjectManagerImpl;
import org.datanucleus.PersistenceConfiguration;
import org.datanucleus.api.jpa.beanvalidation.BeanValidatorHandlerFactory;
import org.datanucleus.api.jpa.criteria.CriteriaBuilderImpl;
import org.datanucleus.api.jpa.criteria.CriteriaQueryImpl;
import org.datanucleus.api.jpa.metamodel.MetamodelImpl;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusObjectNotFoundException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.QueryLanguage;
import org.datanucleus.metadata.QueryMetaData;
import org.datanucleus.metadata.QueryResultMetaData;
import org.datanucleus.metadata.TransactionType;
import org.datanucleus.query.compiler.QueryCompilation;
import org.datanucleus.state.CallbackHandler;
import org.datanucleus.state.DetachState;
import org.datanucleus.state.lock.LockManager;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.StringUtils;

/**
* EntityManager implementation for JPA.
*/
public class JPAEntityManager implements EntityManager
{
    /** Localisation utility for output messages */
    protected static final Localiser LOCALISER = Localiser.getInstance("org.datanucleus.api.jpa.Localisation",
        NucleusJPAHelper.class.getClassLoader());

    /** The underlying ObjectManager managing the persistence. */
    protected ObjectManager om;

    /** Parent EntityManagerFactory. */
    protected EntityManagerFactory emf;

    /** Current Transaction (when using ResourceLocal). Will be null if using JTA. */
    protected EntityTransaction tx;

    /** The Flush Mode. */
    protected FlushModeType flushMode = FlushModeType.AUTO;

    /** Type of Persistence Context */
    protected PersistenceContextType persistenceContextType;

    /** Fetch Plan (extension). */
    protected JPAFetchPlan fetchPlan = null;

    /**
     * Constructor.
     * @param theEMF The parent EntityManagerFactory
     * @param nucleusCtx Nucleus Context
     * @param contextType The Persistence Context type
     */
    public JPAEntityManager(EntityManagerFactory theEMF, NucleusContext nucleusCtx, PersistenceContextType contextType)
    {
        emf = theEMF;
        persistenceContextType = contextType;

        // Allocate our ObjectManager
        om = new ObjectManagerImpl(nucleusCtx, new JPAPersistenceManager(this),
            nucleusCtx.getPersistenceConfiguration().getStringProperty("datanucleus.ConnectionUserName"),
            nucleusCtx.getPersistenceConfiguration().getStringProperty("datanucleus.ConnectionPassword"));

        if (nucleusCtx.getPersistenceConfiguration().getStringProperty("datanucleus.TransactionType").equalsIgnoreCase(
            TransactionType.RESOURCE_LOCAL.toString()))
        {
            // Using ResourceLocal transaction so allocate a transaction
            tx = new JPAEntityTransaction(om);
        }

        CallbackHandler beanValidator = BeanValidatorHandlerFactory.newInstance(om);
        if (beanValidator != null)
        {
            om.getCallbackHandler().addListener(beanValidator, null);
        }

        fetchPlan = new JPAFetchPlan(om.getFetchPlan());
    }

    /**
     * Clear the persistence context, causing all managed entities to become detached.
     * Changes made to entities that have not been flushed to the database will not be persisted.
     */
    public void clear()
    {
        assertIsOpen();
        om.detachAll();
        om.clearDirty();
        om.evictAllObjects();
    }

    /**
     * Determine whether the EntityManager is open.
     * @return true until the EntityManager has been closed.
     */
    public boolean isOpen()
    {
        return !om.isClosed();
    }

    public ExecutionContext getExecutionContext()
    {
        return om;
    }

    /**
     * Close an application-managed EntityManager.
     * After the close method has been invoked, all methods on the EntityManager instance and any Query objects obtained
     * from it will throw the  IllegalStateException except for getTransaction and isOpen (which will return false).
     * If this method is called when the EntityManager is associated with an active transaction, the persistence context
     * remains managed until the transaction completes.
     * @throws IllegalStateException if the EntityManager is container-managed.
     */
    public void close()
    {
        assertIsOpen();

        if (((JPAEntityManagerFactory)emf).isContainerManaged())
        {
            // JPA2 spec and javadocs
            throw new IllegalStateException(LOCALISER.msg("EM.ContainerManagedClose"));
        }

        try
        {
            om.close();
        }
        catch (NucleusException ne)
        {
            throw NucleusJPAHelper.getJPAExceptionForNucleusException(ne);
        }
    }

    /**
     * Return the entity manager factory for the entity manager.
     * @return EntityManagerFactory instance
     * @throws IllegalStateException if the entity manager has
     * been closed.
     */
    public EntityManagerFactory getEntityManagerFactory()
    {
        return emf;
    }

    /**
     * Acessor for the current FetchPlan
     * @return The FetchPlan
     */
    public JPAFetchPlan getFetchPlan()
    {
        return fetchPlan;
    }

    /**
     * Check if the instance belongs to the current persistence context.
     * @param entity
     * @return Whether it is contained in the current context
     * @throws IllegalArgumentException if not an entity
     */
    public boolean contains(Object entity)
    {
        assertIsOpen();
        assertEntity(entity);
        if (om.getApiAdapter().getExecutionContext(entity) != om)
        {
            return false;
        }
        if (om.getApiAdapter().isDeleted(entity))
        {
            return false;
        }
        if (om.getApiAdapter().isDetached(entity))
        {
            return false;
        }
        return true;
    }

    /**
     * Method to find an object from its primary key.
     * @param entityClass The entity class
     * @param primaryKey The PK value
     * @return the found entity instance or null if the entity does not exist
     * @throws IllegalArgumentException if the first argument does not denote an entity type or the second argument is
     *     not a valid type for that entity's primary key
     */
    public Object find(Class entityClass, Object primaryKey)
    {
        return find(entityClass, primaryKey, null, null);
    }

    /**
     * Find by primary key, using the specified properties.
     * Search for an entity of the specified class and primary key.
     * If the entity instance is contained in the persistence context it is returned from there.
     * If a vendor-specific property or hint is not recognised, it is silently ignored.
     * @param entityClass Class of the entity required
     * @param primaryKey The PK value
     * @param properties standard and vendor-specific properties
     * @return the found entity instance or null if the entity does not exist
     * @throws IllegalArgumentException if the first argument does not denote an entity type or the
     *     second argument is is not a valid type for that entity's primary key or is null
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties)
    {
        return (T)find(entityClass, primaryKey, null, properties);
    }

    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lock)
    {
        return (T)find(entityClass, primaryKey, null, null);
    }

    /**
     * Method to return the persistent object of the specified entity type with the provided PK.
     * @param entityClass Entity type
     * @param primaryKey PK. Can be an instanceof the PK type, or the key when using single-field
     * @param lock Any locking to apply
     * @param properties Any optional properties to control the operation
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lock, Map<String, Object> properties)
    {
        // TODO Use properties
        assertLockModeValid(lock);
        assertIsOpen();
        assertEntity(entityClass);

        AbstractClassMetaData acmd = om.getMetaDataManager().getMetaDataForClass(entityClass, om.getClassLoaderResolver());
        if (acmd == null)
        {
            throwException(new EntityNotFoundException());
        }

        Object pc;
        try
        {
            // Get the identity
            Object id = primaryKey;
            if (!acmd.getObjectidClass().equals(primaryKey.getClass().getName()))
            {
                // primaryKey is just the key (when using single-field identity), so create a PK object
                try
                {
                    id = om.newObjectId(entityClass, primaryKey);
                }
                catch (NucleusException ne)
                {
                    throw new IllegalArgumentException(ne);
                }
            }

            if (lock != null && lock != LockModeType.NONE)
            {
                // Register the object for locking
                om.getLockManager().lock(id, getLockTypeForJPALockModeType(lock));
            }

            pc = om.findObject(id, true, true, null);
        }
        catch (NucleusObjectNotFoundException ex)
        {
            // in JPA, if object not found return null
            return null;
        }

        if (om.getApiAdapter().isTransactional(pc))
        {
            // transactional instances are not validated, so we check if a deleted instance has been flushed
            ObjectProvider sm = om.findObjectProvider(pc);
            if (om.getApiAdapter().isDeleted(pc))
            {
                try
                {
                    sm.locate();
                }
                catch (NucleusObjectNotFoundException ex)
                {
                    // the instance has been flushed, and it was not found, so we return null
                    return null;
                }
            }
        }
        return (T)pc;
    }

    /**
     * Return the underlying provider object for the EntityManager, if available.
     * The result of this method is implementation specific.
     */
    public Object getDelegate()
    {
        assertIsOpen();

        return om;
    }

    /**
     * Return an object of the specified type to allow access to the provider-specific API.
     * If the provider's EntityManager implementation does not support the specified class, the
     * PersistenceException is thrown.
     * @param cls the class of the object to be returned. This is normally either the underlying
     * EntityManager implementation class or an interface that it implements.
     * @return an instance of the specified class
     * @throws PersistenceException if the provider does not support the call.
     */
    public <T> T unwrap(Class<T> cls)
    {
        if (cls == ObjectManager.class || cls == ObjectManagerImpl.class)
        {
            return (T) om;
        }

        return (T)throwException(new PersistenceException("Not yet supported"));
    }

    /**
     * Get an instance, whose state may be lazily fetched. If the requested
     * instance does not exist in the database, the EntityNotFoundException is
     * thrown when the instance state is first accessed. The persistence
     * provider runtime is permitted to throw the EntityNotFoundException when
     * getReference is called. The application should not expect that the
     * instance state will be available upon detachment, unless it was accessed
     * by the application while the entity manager was open.
     * @param entityClass Class of the entity
     * @param primaryKey The PK
     * @return the found entity instance
     * @throws IllegalArgumentException if the first argument does not denote an entity type or the second argument is not
     *     a valid type for that entities PK
     * @throws EntityNotFoundException if the entity state cannot be accessed
     */
    @SuppressWarnings("unchecked")
    public Object getReference(Class entityClass, Object primaryKey)
    {
        assertIsOpen();
        assertEntity(entityClass);

        Object id = null;
        try
        {
            id = om.newObjectId(entityClass, primaryKey);
        }
        catch (NucleusException ne)
        {
            throw new IllegalArgumentException(ne);
        }

        try
        {
            return om.findObject(id, false, false, null);
        }
        catch (NucleusObjectNotFoundException ne)
        {
            throw NucleusJPAHelper.getJPAExceptionForNucleusException(ne);
        }
    }

    /**
     * Set the lock mode for an entity object contained in the persistence context.
     * @param entity The Entity
     * @param lockMode Lock mode
     * @throws PersistenceException if an unsupported lock call is made
     * @throws IllegalArgumentException if the instance is not an entity or is a detached entity
     * @throws TransactionRequiredException if there is no transaction
     */
    public void lock(Object entity, LockModeType lockMode)
    {
        lock(entity, lockMode, null);
    }

    /**
     * Set the lock mode for an entity object contained in the persistence context.
     * @param entity The Entity
     * @param lock Lock mode
     * @param properties Optional properties controlling the operation
     * @throws PersistenceException if an unsupported lock call is made
     * @throws IllegalArgumentException if the instance is not an entity or is a detached entity
     * @throws TransactionRequiredException if there is no transaction
     */
    public void lock(Object entity, LockModeType lock, Map<String, Object> properties)
    {
        // TODO Use properties
        assertLockModeValid(lock);
        assertIsOpen();
        assertIsActive();
        assertEntity(entity);
        if (om.getApiAdapter().isDetached(entity))
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityIsDetached",
                StringUtils.toJVMIDString(entity), "" + om.getApiAdapter().getIdForObject(entity)));
        }
        if (!contains(entity))
        {
            // The object is not contained (the javadoc doesnt explicitly say which exception to throw here)
            throwException(new PersistenceException("Entity is not contained in this persistence context so cant lock it"));
        }

        AbstractClassMetaData cmd =
            om.getMetaDataManager().getMetaDataForClass(entity.getClass(), om.getClassLoaderResolver());
        if (lock == LockModeType.OPTIMISTIC || lock == LockModeType.OPTIMISTIC_FORCE_INCREMENT &&
            !cmd.hasVersionStrategy())
        {
            throw new PersistenceException("Object of type " + entity.getClass().getName() +
                " is not versioned so cannot lock optimistically!");
        }

        if (lock != null && lock != LockModeType.NONE)
        {
            om.getLockManager().lock(om.findObjectProvider(entity), getLockTypeForJPALockModeType(lock));
        }
    }

    /**
     * Make an instance managed and persistent.
     * @param entity The Entity
     * @throws EntityExistsException if the entity already exists.
     *     (The EntityExistsException may be thrown when the persist operation is invoked,
     *     or the EntityExistsException/PersistenceException may be thrown at flush/commit time.)
     * @throws IllegalArgumentException if not an entity
     * @throws TransactionRequiredException if invoked on a container-managed entity manager
     *     of type PersistenceContextType.TRANSACTION and there is no transaction.
     */
    public void persist(Object entity)
    {
        assertIsOpen();
        assertTransactionNotRequired();
        assertEntity(entity);
        if (om.exists(entity))
        {
            if (om.getApiAdapter().isDetached(entity))
            {
                // The JPA spec is very confused about when this exception is thrown, however the JPA TCK
                // invokes this operation multiple times over the same instance
                // Entity is already persistent. Maybe the ObjectManager.exists method isnt the best way of checking
                throwException(new EntityExistsException(LOCALISER.msg("EM.EntityIsPersistent", StringUtils.toJVMIDString(entity))));
            }
        }

        try
        {
            om.persistObject(entity, false);
        }
        catch (NucleusException ne)
        {
            throwException(NucleusJPAHelper.getJPAExceptionForNucleusException(ne));
        }
    }

    /**
     * Merge the state of the given entity into the current persistence context.
     * @param entity The Entity
     * @return the instance that the state was merged to
     * @throws IllegalArgumentException if instance is not an entity or is a removed entity
     * @throws TransactionRequiredException if invoked on a container-managed entity manager
     *     of type PersistenceContextType.TRANSACTION and there is no transaction.
     */
    public Object merge(Object entity)
    {
        assertIsOpen();
        assertTransactionNotRequired();
        assertEntity(entity);
        if (om.getApiAdapter().isDeleted(entity))
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityIsDeleted",
                StringUtils.toJVMIDString(entity), "" + om.getApiAdapter().getIdForObject(entity)));
        }

        try
        {
            return om.persistObject(entity, true);
        }
        catch (NucleusException ne)
        {
            return throwException(NucleusJPAHelper.getJPAExceptionForNucleusException(ne));
        }
    }

    /**
     * Remove the given entity from the persistence context, causing a managed entity to become
     * detached. Unflushed changes made to the entity if any (including removal of the entity),
     * will not be synchronized to the database. Entities which previously referenced the detached
     * entity will continue to reference it.
     * @param entity
     * @throws IllegalArgumentException if the instance is not an entity
     */
    public void detach(Object entity)
    {
        assertIsOpen();
        assertEntity(entity);

        try
        {
            if (om.getApiAdapter().isDirty(entity))
            {
                // The JPA spec doesn't actually say what to do with a newly persistent object
                // We simply flush it (which goes against "unflushed changes made to the entity will not be synced")
                // but then what are we supposed to do with a newly persistent ? does it make it into the DB after?
                om.flushInternal(true);
            }
            om.detachObject(entity, new DetachState(om.getApiAdapter()));
        }
        catch (NucleusException ne)
        {
            throwException(NucleusJPAHelper.getJPAExceptionForNucleusException(ne));
        }
    }

    /**
     * Refresh the state of the instance from the database, overwriting changes made to the entity, if any.
     * @param entity The Entity
     * @throws IllegalArgumentException if not an entity or entity is not managed
     * @throws TransactionRequiredException if invoked on a container-managed entity manager
     *     of type PersistenceContextType.TRANSACTION and there is no transaction.
     * @throws EntityNotFoundException if the entity no longer exists in the database
     */
    public void refresh(Object entity)
    {
        refresh(entity, null, null);
    }

    /**
     * Refresh the state of the instance from the database, using the specified properties,
     * and overwriting changes made to the entity, if any.
     * If a vendor-specific property or hint is not recognised, it is silently ignored.
     * @param entity
     * @param properties standard and vendor-specific properties
     * @throws IllegalArgumentException if the instance is not
     * an entity or the entity is not managed
     * @throws TransactionRequiredException if invoked on a container-managed entity manager
     *     of type PersistenceContextType.TRANSACTION and there is no transaction.
     * @throws EntityNotFoundException if the entity no longer exists in the database
     */
    public void refresh(Object entity, Map<String, Object> properties)
    {
        refresh(entity, null, properties);
    }

    public void refresh(Object entity, LockModeType lock)
    {
        refresh(entity, lock, null);
    }

    public void refresh(Object entity, LockModeType lock, Map<String, Object> properties)
    {
        // TODO Use properties
        assertLockModeValid(lock);
        assertIsOpen();
        assertTransactionNotRequired();
        assertEntity(entity);
        if (om.getApiAdapter().getExecutionContext(entity) != om)
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityIsNotManaged", StringUtils.toJVMIDString(entity)));
        }
        if (!om.exists(entity))
        {
            throwException(new EntityNotFoundException(LOCALISER.msg("EM.EntityNotInDatastore", StringUtils.toJVMIDString(entity))));
        }

        try
        {
            if (lock != null && lock != LockModeType.NONE)
            {
                // Register the object for locking
                om.getLockManager().lock(om.getApiAdapter().getIdForObject(entity), getLockTypeForJPALockModeType(lock));
            }

            om.refreshObject(entity);
        }
        catch (NucleusException ne)
        {
            throwException(NucleusJPAHelper.getJPAExceptionForNucleusException(ne));
        }
    }

    /**
     * Remove the entity instance.
     * @param entity The Entity
     * @throws IllegalArgumentException if not an entity or if a detached entity
     * @throws TransactionRequiredException if invoked on a container-managed entity manager
     *     of type PersistenceContextType.TRANSACTION and there is no transaction.
     */
    public void remove(Object entity)
    {
        assertIsOpen();
        assertTransactionNotRequired();
        if (entity == null)
        {
            return;
        }
        assertEntity(entity);

        // What if the object doesnt exist in the datastore ? IllegalArgumentException. Spec says nothing
        if (om.getApiAdapter().isDetached(entity))
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityIsDetached",
                StringUtils.toJVMIDString(entity), "" + om.getApiAdapter().getIdForObject(entity)));
        }

        try
        {
            om.deleteObject(entity);
        }
        catch (NucleusException ne)
        {
            throwException(NucleusJPAHelper.getJPAExceptionForNucleusException(ne));
        }
    }

    /**
     * Synchronize the persistence context to the underlying database.
     * @throws TransactionRequiredException if there is no transaction
     * @throws PersistenceException if the flush fails
     */
    public void flush()
    {
        assertIsOpen();
        assertIsActive();

        try
        {
            om.flush();
        }
        catch (NucleusException ne)
        {
            throwException(NucleusJPAHelper.getJPAExceptionForNucleusException(ne));
        }
    }

    /**
     * Get the flush mode that applies to all objects contained in the persistence context.
     * @return flushMode
     */
    public FlushModeType getFlushMode()
    {
        assertIsOpen();
        return flushMode;
    }

    /**
     * Set the flush mode that applies to all objects contained in the persistence context.
     * @param flushMode Mode of flush
     */
    public void setFlushMode(FlushModeType flushMode)
    {
        assertIsOpen();
        this.flushMode = flushMode;
    }

    /**
     * Get the current lock mode for the entity instance.
     * @param entity The entity in question
     * @return lock mode
     * @throws TransactionRequiredException if there is no transaction
     * @throws IllegalArgumentException if the instance is not a managed entity and a transaction is active
     */
    public LockModeType getLockMode(Object entity)
    {
        assertIsActive();
        assertEntity(entity);
        if (om.getApiAdapter().getExecutionContext(entity) != om)
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityIsNotManaged", StringUtils.toJVMIDString(entity)));
        }
        if (om.getApiAdapter().isDetached(entity))
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityIsNotManaged", StringUtils.toJVMIDString(entity)));
        }

        ObjectProvider sm = om.findObjectProvider(entity);
        return getJPALockModeTypeForLockType(sm.getLockMode());
    }

    // ------------------------------------ Transactions --------------------------------------

    /**
     * Return the resource-level transaction object.
     * The EntityTransaction instance may be used serially to begin and commit multiple transactions.
     * @return EntityTransaction instance
     * @throws IllegalStateException if invoked on a JTA EntityManager.
     */
    public EntityTransaction getTransaction()
    {
        if (tx == null)
        {
            throw new IllegalStateException(LOCALISER.msg("EM.TransactionNotLocal"));
        }

        return tx;
    }

    /**
     * Indicate to the EntityManager that a JTA transaction is active.
     * This method should be called on a JTA application managed EntityManager that was created
     * outside the scope of the active transaction to associate it with the current JTA transaction.
     * @throws TransactionRequiredException if there is no transaction.
     */
    public void joinTransaction()
    {
        assertIsOpen();
        //assertIsActive();
        //TODO assertNotActive
        //assertTransactionNotRequired();
        tx = new JPAEntityTransaction(om);

        // TODO Implement joinTransaction()
    }

    // ------------------------------------ Query Methods --------------------------------------

    /**
     * Method to return a query for the specified Criteria Query.
     * @param criteriaQuery The Criteria query
     * @return The JPA query to use
     */
    public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery)
    {
        CriteriaQueryImpl<T> criteria = (CriteriaQueryImpl<T>)criteriaQuery;
        String jpqlString = criteria.toString();
        TypedQuery<T> query = null;
        QueryCompilation compilation = criteria.getCompilation(om.getMetaDataManager(), om.getClassLoaderResolver());
        if (criteria.getResultType() != null && criteria.getResultType() != compilation.getCandidateClass())
        {
            query = createQuery(jpqlString, criteria.getResultType());
        }
        else
        {
            query = createQuery(jpqlString);
        }
        org.datanucleus.store.query.Query internalQuery = ((JPAQuery)query).getInternalQuery();
        if (compilation.getExprResult() == null)
        {
            // If the result was "Object(e)" or "e" then this is meaningless so remove
            internalQuery.setResult(null);
        }
        internalQuery.setCompilation(compilation);

        return query;
    }

    /**
     * Return an instance of QueryBuilder for the creation of Criteria API QueryDefinition objects.
     * @return QueryBuilder instance
     * @throws IllegalStateException if the entity manager has been closed.
     */
    public CriteriaBuilder getCriteriaBuilder()
    {
        assertIsOpen();

        return new CriteriaBuilderImpl((MetamodelImpl) getMetamodel());
    }

    /**
     * Create an instance of Query for executing a named query (in JPQL or SQL).
     * @param queryName the name of a query defined in metadata
     * @return the new query instance
     * @throws IllegalArgumentException if a query has not been defined with the given name
     */
    public <T> TypedQuery<T> createNamedQuery(String queryName, Class<T> resultClass)
    {
        return createNamedQuery(queryName).setResultClass(resultClass);
    }

    /**
     * Create an instance of Query for executing a named query (in JPQL or SQL).
     * @param queryName the name of a query defined in metadata
     * @return the new query instance
     * @throws IllegalArgumentException if a query has not been defined with the given name
     */
    public JPAQuery createNamedQuery(String queryName)
    {
        assertIsOpen();

        if (queryName == null)
        {
            throw new IllegalArgumentException(LOCALISER.msg("Query.NamedQueryNotFound", queryName));
        }

        // Find the Query for the specified class
        ClassLoaderResolver clr = om.getClassLoaderResolver();
        QueryMetaData qmd = om.getMetaDataManager().getMetaDataForQuery(null, clr, queryName);
        if (qmd == null)
        {
            throw new IllegalArgumentException(LOCALISER.msg("Query.NamedQueryNotFound", queryName));
        }

        // Create the Query
        try
        {
            if (qmd.getLanguage().equals(QueryLanguage.JPQL.toString()))
            {
                // "named-query" so return JPQL
                org.datanucleus.store.query.Query internalQuery = om.getStoreManager().getQueryManager().newQuery(
                    qmd.getLanguage().toString(), om, qmd.getQuery());
                return new JPAQuery(this, internalQuery, qmd.getLanguage());
            }
            else if (qmd.getLanguage().equals(QueryLanguage.SQL.toString()))
            {
                // "named-native-query" so return SQL
                org.datanucleus.store.query.Query internalQuery = om.getStoreManager().getQueryManager().newQuery(
                    qmd.getLanguage(), om, qmd.getQuery());
                if (qmd.getResultClass() != null)
                {
                    // Named SQL query with result class
                    String resultClassName = qmd.getResultClass();
                    Class resultClass = null;
                    try
                    {
                        resultClass = om.getClassLoaderResolver().classForName(resultClassName);
                        internalQuery.setResultClass(resultClass);
                        return new JPAQuery(this, internalQuery, qmd.getLanguage());
                    }
                    catch (Exception e)
                    {
                        // Result class not found so throw exception (not defined in the JPA spec)
                        throw new IllegalArgumentException(LOCALISER.msg("Query.ResultClassNotFound", qmd.getName(), resultClassName));
                    }
                }
                else if (qmd.getResultMetaDataName() != null)
                {
                    QueryResultMetaData qrmd = om.getMetaDataManager().getMetaDataForQueryResult(qmd.getResultMetaDataName());
                    if (qrmd == null)
                    {
                        // TODO Localise this
                        throw new IllegalArgumentException("ResultSetMapping " + qmd.getResultMetaDataName() + " is not found");
                    }
                    internalQuery.setResultMetaData(qrmd);
                    return new JPAQuery(this, internalQuery, qmd.getLanguage());
                }
                else
                {
                    return new JPAQuery(this, internalQuery, qmd.getLanguage());
                }
            }
            else
            {
                throw new IllegalArgumentException(LOCALISER.msg("Query.LanguageNotSupportedByStore", qmd.getLanguage()));
            }
        }
        catch (NucleusException ne)
        {
            throw new IllegalArgumentException(ne.getMessage(), ne);
        }
    }

    /**
     * Create an instance of Query for executing an SQL statement.
     * @param sqlString a native SQL query string
     * @return the new query instance
     */
    public Query createNativeQuery(String sqlString)
    {
        return createNativeQuery(sqlString, (Class)null);
    }

    /**
     * Create an instance of Query for executing an SQL query.
     * @param sqlString a native SQL query string
     * @param resultClass the class of the resulting instance(s)
     * @return the new query instance
     */
    public Query createNativeQuery(String sqlString, Class resultClass)
    {
        assertIsOpen();
        try
        {
            org.datanucleus.store.query.Query internalQuery = om.getStoreManager().getQueryManager().newQuery(
                QueryLanguage.SQL.toString(), om, sqlString);
            if (resultClass != null)
            {
                internalQuery.setResultClass(resultClass);
            }
            return new JPAQuery(this, internalQuery, QueryLanguage.SQL.toString());
        }
        catch (NucleusException ne)
        {
            throw new IllegalArgumentException(ne.getMessage(), ne);
        }
    }

    /**
     * Create an instance of Query for executing an SQL query.
     * @param sqlString a native SQL query string
     * @param resultSetMapping the name of the result set mapping
     * @return the new query instance
     */
    public Query createNativeQuery(String sqlString, String resultSetMapping)
    {
        assertIsOpen();
        try
        {
            org.datanucleus.store.query.Query internalQuery = om.getStoreManager().getQueryManager().newQuery(
                QueryLanguage.SQL.toString(), om, sqlString);
            QueryResultMetaData qrmd = om.getMetaDataManager().getMetaDataForQueryResult(resultSetMapping);
            if (qrmd == null)
            {
                // TODO Localise this
                throw new IllegalArgumentException("ResultSetMapping " + resultSetMapping + " is not found");
            }
            internalQuery.setResultMetaData(qrmd);
            return new JPAQuery(this, internalQuery, QueryLanguage.SQL.toString());
        }
        catch (NucleusException ne)
        {
            throw new IllegalArgumentException(ne.getMessage(), ne);
        }
    }

    /* (non-Javadoc)
     * @see javax.persistence.EntityManager#createQuery(java.lang.String, java.lang.Class)
     */
    public <T> TypedQuery<T> createQuery(String queryString, Class<T> resultClass)
    {
        return createQuery(queryString).setResultClass(resultClass);
    }

    /**
     * Create an instance of Query for executing a JPQL statement.
     * @param queryString a Java Persistence query string
     * @return the new query instance
     * @throws IllegalArgumentException if query string is not valid
     */
    public JPAQuery createQuery(String queryString)
    {
        assertIsOpen();
        try
        {
            org.datanucleus.store.query.Query internalQuery = om.getStoreManager().getQueryManager().newQuery(
                QueryLanguage.JPQL.toString(), om, queryString);
            return new JPAQuery(this, internalQuery, QueryLanguage.JPQL.toString());
        }
        catch (NucleusException ne)
        {
            throw new IllegalArgumentException(ne.getMessage(), ne);
        }
    }

    /**
     * Set an entity manager property.
     * If a vendor-specific property is not recognized, it is silently ignored.
     * @param propertyName Name of the property
     * @param value The value
     * @throws IllegalArgumentException if the second argument is not valid for the implementation
     */
    public void setProperty(String propertyName, Object value)
    {
        try
        {
            om.setProperty(propertyName, value);
        }
        catch (Exception e)
        {
            throw new IllegalArgumentException("Property '" + propertyName + "' value=" + value + " invalid");
        }
    }

    /**
     * Get the properties and associated values that are in effect for the entity manager.
     * Changing the contents of the map does not change the configuration in effect.
     */
    public Map<String,Object> getProperties()
    {
        return om.getProperties();
    }

    /**
     * Get the names of the properties that are supported for use with the entity manager.
     * These correspond to properties and hints that may be passed to the methods of the EntityManager
     * interface that take a properties argument or used with the PersistenceContext annotation.
     * These properties include all standard entity manager hints and properties as well as
     * vendor-specific one supported by the provider. These properties may or may not currently be in effect.
     * @return property names Names of the properties accepted
     */
    public Set<String> getSupportedProperties()
    {
        return om.getSupportedProperties();
    }

    /**
     * Return an instance of Metamodel interface for access to the metamodel of the persistence unit.
     * @return Metamodel instance
     * @throws IllegalStateException if the entity manager has been closed.
     */
    public Metamodel getMetamodel()
    {
        return emf.getMetamodel();
    }

    // ------------------------------------------ Assertions --------------------------------------------

    /**
     * Assert if the EntityManager is closed.
     * @throws IllegalStateException When the EntityManaged is closed
     */
    private void assertIsOpen()
    {
        if (om.isClosed())
        {
            throw new IllegalStateException(LOCALISER.msg("EM.IsClosed"));
        }
    }

    /**
     * Assert if the transaction is not active.
     * @throws TransactionRequiredException When the EntityManaged is closed
     */
    private void assertIsActive()
    {
        if (!isTransactionActive())
        {
            throw new TransactionRequiredException(LOCALISER.msg("EM.TransactionRequired"));
        }
    }

    /**
     * @return true if there is an active transaction associated to this EntityManager
     */   
    private boolean isTransactionActive()
    {
        return tx != null && tx.isActive();
    }

    /**
     * Method to throw a TransactionRequiredException if the provided lock mode is not valid
     * for the current transaction situation (i.e if lock mode other than NONE is specified then
     * the transaction has to be active).
     * @param lock The lock mode
     */
    private void assertLockModeValid(LockModeType lock)
    {
        if (lock != null && lock != LockModeType.NONE && !isTransactionActive())
        {
            throw new TransactionRequiredException(LOCALISER.msg("EM.TransactionRequired"));
        }
    }

    /**
     * Assert if the passed entity is not persistable, or has no persistence information.
     * @param entity The entity (or class of the entity)
     * @throws IllegalArgumentException Thrown if not an entity
     */
    private void assertEntity(Object entity)
    {
        if (entity == null)
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityNotAnEntity", entity));
        }

        Class cls = null;
        if (entity instanceof Class)
        {
            // Class passed in so just check that
            cls = (Class)entity;
        }
        else
        {
            // Object passed in so check its class
            cls = entity.getClass();
        }

        try
        {
            om.assertClassPersistable(cls);
        }
        catch (NucleusException ne)
        {
            throw new IllegalArgumentException(LOCALISER.msg("EM.EntityNotAnEntity", cls.getName()), ne);
        }
    }

    /**
     * Assert if the persistence context is TRANSACTION, and the transaction is not active.
     * @throws TransactionRequiredException thrown if the context requires a txn but isnt active
     */
    private void assertTransactionNotRequired()
    {
        if (persistenceContextType == PersistenceContextType.TRANSACTION && !isTransactionActive())
        {
            throw new TransactionRequiredException(LOCALISER.msg("EM.TransactionRequired"));
        }
    }

    /**
     * Convenience method to throw the supplied exception.
     * If the supplied exception is a PersistenceException then also marks the current transaction for rollback.
     * @param re The exception
     */
    private Object throwException(RuntimeException re)
    {
        if (re instanceof PersistenceException)
        {
            PersistenceConfiguration conf = om.getNucleusContext().getPersistenceConfiguration();
            if (tx.isActive())
            {
                boolean markForRollback = conf.getBooleanProperty("datanucleus.jpa.txnMarkForRollbackOnException");
                if (markForRollback)
                {
                    // The JPA spec says that all PersistenceExceptions thrown should mark the transaction for
                    // rollback. Seems stupid to me. e.g 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. FFS.
                    getTransaction().setRollbackOnly();
                }
            }
        }
        throw re;
    }

    /**
     * Convenience method to convert from the JPA LockModeType to the type expected by LockManager
     * @param lock JPA LockModeType
     * @return The lock type
     */
    public static short getLockTypeForJPALockModeType(LockModeType lock)
    {
        short lockModeType = LockManager.LOCK_MODE_NONE;
        if (lock == LockModeType.OPTIMISTIC || lock == LockModeType.READ)
        {
            lockModeType = LockManager.LOCK_MODE_OPTIMISTIC_READ;
        }
        else if (lock == LockModeType.OPTIMISTIC_FORCE_INCREMENT || lock == LockModeType.WRITE)
        {
            lockModeType = LockManager.LOCK_MODE_OPTIMISTIC_WRITE;
        }
        else if (lock == LockModeType.PESSIMISTIC_READ)
        {
            lockModeType = LockManager.LOCK_MODE_PESSIMISTIC_READ;
        }
        else if (lock == LockModeType.PESSIMISTIC_FORCE_INCREMENT || lock == LockModeType.PESSIMISTIC_WRITE)
        {
            lockModeType = LockManager.LOCK_MODE_PESSIMISTIC_WRITE;
        }
        return lockModeType;
    }

    /**
     * Convenience method to convert from LockManager lock type to JPA LockModeType
     * @param lockType Lock type
     * @return JPA LockModeType
     */
    public static LockModeType getJPALockModeTypeForLockType(short lockType)
    {
        if (lockType == LockManager.LOCK_MODE_OPTIMISTIC_READ)
        {
            return LockModeType.OPTIMISTIC;
        }
        else if (lockType == LockManager.LOCK_MODE_OPTIMISTIC_WRITE)
        {
            return LockModeType.OPTIMISTIC_FORCE_INCREMENT;
        }
        else if (lockType == LockManager.LOCK_MODE_PESSIMISTIC_READ)
        {
            return LockModeType.PESSIMISTIC_READ;
        }
        else if (lockType == LockManager.LOCK_MODE_PESSIMISTIC_WRITE)
        {
            return LockModeType.PESSIMISTIC_WRITE;
        }
        return LockModeType.NONE;
    }
}
TOP

Related Classes of org.datanucleus.api.jpa.JPAEntityManager

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.