All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openbp.server.persistence.jpa.JpaPersistenceContextProvider Maven / Gradle / Ivy

The newest version!
/*
 *   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.
 */
package org.openbp.server.persistence.jpa;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.openbp.common.logger.LogUtil;
import org.openbp.common.util.ToStringHelper;
import org.openbp.server.persistence.BasicPersistenceContextProvider;
import org.openbp.server.persistence.PersistenceContext;
import org.openbp.server.persistence.PersistenceContextProvider;
import org.openbp.server.persistence.PersistenceCriterion;
import org.openbp.server.persistence.PersistenceException;
import org.openbp.server.persistence.PersistenceOrdering;
import org.openbp.server.persistence.PersistenceQuery;
import org.openbp.server.persistence.PersistentObjectBase;
import org.openbp.server.persistence.PersistentObjectNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

/**
 * Peristence context provider for JPA-based persistence.
 * Since JPA/Spring Framework handles alls the magic of keeping track of entity managers and transactions, we can combine the context provide and the context object.
 *
 * @author Heiko Erhardt
 */
@Repository
public class JpaPersistenceContextProvider extends BasicPersistenceContextProvider
	implements PersistenceContext
{
	/**
	 * Entity manager factory.
	 * If provided, we will have this one autowired in order to comply with Spring environments.
	 * If not, we will resolve it using the JPA Persitence object.
	 */
	@Autowired(required = false)
	protected transient EntityManagerFactory entityManagerFactory;

	/** Underlying JPA entity manager */
	@javax.persistence.PersistenceContext
	protected EntityManager entityManager;

	/** Transaction definition in case we need to create a transaction */
	private TransactionDefinition transactionDefinition;

	/** Spring's transaction manager, if any */
	@Autowired(required = false)
	protected PlatformTransactionManager springTransactionMgr;

	/** Name of the persistent unit as specified in the file "persistence.xml" */
	protected String persistentUnitName;

	/**
	 * Constructor.
	 */
	public JpaPersistenceContextProvider()
	{
		DefaultTransactionDefinition td = new DefaultTransactionDefinition();
		td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
		td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
		transactionDefinition = td;

		// Default: Non-transactional behaviour
		setTransactional(false);
	}

	/**
	 * Gets the the only persistence context.
	 */
	public PersistenceContext getPersistenceContext()
	{
		return this;
	}

	public void shutdown()
	{
		release();
	}

	@Override
	protected PersistenceContext createPersistenceContext()
		throws PersistenceException
	{
		return this;
	}

	@Override
	public PersistenceContext obtainPersistenceContext()
		throws PersistenceException
	{
		return this;
	}

	@Override
	public PersistenceContext obtainExistingPersistenceContext()
	{
		return this;
	}

	/**
	 * Sets the underlying entity manager factory.
	 * If no entity manager factory has been supplied, it will be accessed via the entity manager itself.
	 * @param entityManagerFactory The entity manager factory
	 */
	public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory)
	{
		this.entityManagerFactory = entityManagerFactory;
	}

	/**
	 * Sets the underlying JPA entity manager.
	 * If no entity manager has been supplied, it will be provided via the PersistenceContext annotation.
	 * @param entityManager The entity manager
	 */
	public void setEntityManager(EntityManager entityManager)
	{
		this.entityManager = entityManager;
	}

	/**
	 * Sets the name of the persistent unit as specified in the file "persistence.xml".
	 */
	public void setPersistentUnitName(final String persistentUnitName)
	{
		this.persistentUnitName = persistentUnitName;
	}

	/**
	 * Returns a string representation of this object.
	 */
	public String toString()
	{
		return ToStringHelper.toString(this);
	}

	/**
	 * Gets the persistence context provider that created this context.
	 */
	public PersistenceContextProvider getPersistenceContextProvider()
	{
		return this;
	}

	/**
	 * Creates a query descriptor object for the given object class.
	 *
	 * @param cls Cls
	 * @return The new query descriptor object
	 */
	public PersistenceQuery createQuery(Class cls)
	{
		return new PersistenceQuery(this, cls);
	}

	/**
	 * Returns a new instance of an entity.
	 *
	 * @param entityClass Entity (interface or object) class that the instance must match.
	 * @return The new entity instance
	 */
	public Object createEntity(Class entityClass)
		throws PersistenceException
	{
		return createEntity(entityClass, this);
	}

	//////////////////////////////////////////////////
	// @@ Helpers
	//////////////////////////////////////////////////

	/**
	 * Creates a persistence exception and logs it.
	 *
	 * @param cause Underlying database exception
	 * @return The exception
	 */
	protected PersistenceException createLoggedException(Throwable cause)
	{
		String msg = LogUtil.error(getClass(), "Persistence error.", cause);
		return new PersistenceException(msg, cause);
	}

	//////////////////////////////////////////////////
	// @@ General
	//////////////////////////////////////////////////

	/**
	 * Releases the context.
	 * The underlying database session will be closed.
	 */
	@Override
	public void release()
	{
		if (entityManager != null)
		{
			try
			{
				LogUtil.debug(getClass(), "Closing JPA session $0.", entityManager);
				entityManager.close();
				entityManager = null;
			}
			catch (Exception e)
			{
				throw createLoggedException(e);
			}
		}
		if (entityManagerFactory != null)
		{
			entityManagerFactory.close();
			entityManagerFactory = null;
		}
	}

	protected Map entityClassToIdPropertyOrFalseMap = new HashMap();

	/**
	 * Determines if the given class is managed by this persistence context.
	 *
	 * @param obj Object to check (usually an object implementing PeristentObject)
	 */
	@Override
	public boolean isPersistentObject(final Object obj)
	{
		try
		{
			entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(obj);
		}
		catch (IllegalArgumentException e)
		{
			return false;
		}
		return true;
	}

	//////////////////////////////////////////////////
	// @@ Object access
	//////////////////////////////////////////////////

	/**
	 * Gets the primary key of the given object.
	 *
	 * @param obj The object
	 * @return The primary key or null if the object is not persistent or if it has not been inserted into the database yet
	 * @throws PersistenceException On error
	 */
	@Override
	public Object getObjectId(final Object obj)
		throws PersistenceException
	{
		try
		{
			return entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(obj);
		}
		catch (IllegalArgumentException e)
		{
			throw new PersistenceException("Class '" + obj.getClass().getName() + "' of object '" + obj
				+ "' is not a persistent class or does not define an id attribute.", e);
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	/**
	 * Merges the (transient) given object with the current session and returns the persistent object.
	 * Reloads the object values from the database.
	 *
	 * @param obj Object to refresh
	 * @return The merged object (might be the same or another instance of the object)
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Object merge(Object obj)
		throws PersistenceException
	{
		try
		{
			if (! getEntityManager().contains(obj))
			{
				if (getObjectId(obj) != null)
				{
					obj = getEntityManager().merge(obj);
				}
				else
				{
					getEntityManager().persist(obj);
				}
			}
			return obj;
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	/**
	 * Refreshes the given persistent object.
	 * Reloads the object values from the database.
	 *
	 * @param obj Object to refresh
	 * @return The refreshed object (might be the same or another instance of the object)
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	@Override
	public Object refreshObject(final Object obj)
		throws PersistenceException
	{
		Object ret = obj;
		if (ret != null)
		{
			try
			{
				if (obj instanceof PersistentObjectBase)
				{
					((PersistentObjectBase) obj).flagAsUnloaded();
				}
				getEntityManager().refresh(ret);
			}
			catch (IllegalArgumentException e)
			{
				// Due to a bug in Hibernate (or the JPA 2.0 spec?) objects that are currently not present in the entity manager cause an IllegalArgumentException.
				Object id = getObjectId(ret);
				try
				{
					Class beanClass = determineEntityClass(ret.getClass());
					ret = findById(id, beanClass);
				}
				catch (Exception e2)
				{
					String msg = LogUtil.error(getClass(), "Persistence error.", e2);
					throw new PersistentObjectNotFoundException(msg, e2);
				}
			}
			catch (EntityNotFoundException e)
			{
				String msg = LogUtil.error(getClass(), "Persistence error.", e);
				throw new PersistentObjectNotFoundException(msg, e);
			}
			catch (Exception e)
			{
				throw createLoggedException(e);
			}
		}
		return ret;
	}

	/**
	 * Removes the given persistent object from the session cache.
	 *
	 * @param obj Object to evict
	 * @throws PersistenceException On error
	 */
	@Override
	public void evict(final Object obj)
		throws PersistenceException
	{
		if (obj != null)
		{
			try
			{
				getEntityManager().detach(obj);
			}
			catch (Exception e)
			{
				throw createLoggedException(e);
			}
		}
	}

	/**
	 * Finds an object by its primary key.
	 *
	 * @param id Primary key
	 * @param entityClass Type of object to lookup (usually a class implementing PeristentObject)
	 * @return The object or null if no such object can be found
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	@Override
	public Object findById(final Object id, final Class entityClass)
		throws PersistenceException
	{
		Class beanClass = determineEntityClass(entityClass);
		try
		{
			Object ret = getEntityManager().find(beanClass, id);
			return ret;
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	/**
	 * Returns a list of the objects of a particular type that match the given criterion.
	 *
	 * @param query Query to run
	 * @return The list or null
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	@Override
	public Iterator runQuery(final PersistenceQuery query)
		throws PersistenceException
	{
		try
		{
			Class beanClass = determineEntityClass(query.getObjectClass());

			CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
			CriteriaQuery cq = cb.createQuery(beanClass);

			Root entity = cq.from(beanClass);
			cq.select(entity);

			for (Iterator it = query.getOrderings(); it.hasNext();)
			{
				PersistenceOrdering ordering = (PersistenceOrdering) it.next();

				if (ordering.isAscending())
					cq.orderBy(cb.asc(entity.get(ordering.getPropertyName())));
				else
					cq.orderBy(cb.desc(entity.get(ordering.getPropertyName())));
			}
			// cq.where(cb.equal(entity.get(Order_.orderNumber), orderNumber));

			for (Iterator it = query.getCriterions(); it.hasNext();)
			{
				PersistenceCriterion criterion = (PersistenceCriterion) it.next();

				String property = criterion.getProperty();
				Path propertyExpression = determinePropertyPath(property, entity);
				String operator = criterion.getOperator();
				Object value = criterion.getOperand();

				if (value != null && isPersistentObject(value))
				{
					value = getObjectId(value);
				}

				Predicate predicate = null;

				if (PersistenceCriterion.OPERATOR_EQ.equals(operator))
				{
					predicate = cb.equal(propertyExpression, value);
				}
				else if (PersistenceCriterion.OPERATOR_EQ_OR_NULL.equals(operator))
				{
					cq.where(predicate = cb.disjunction());
					cq.where(cb.isNull(propertyExpression));
					cq.where(cb.equal(propertyExpression, value));
					continue;
				}
				else if (PersistenceCriterion.OPERATOR_NEQ.equals(operator))
				{
					predicate = cb.notEqual(propertyExpression, value);
				}
				else if (PersistenceCriterion.OPERATOR_GT.equals(operator))
				{
					predicate = cb.gt(propertyExpression, obtainNumber(value, beanClass, property));
				}
				else if (PersistenceCriterion.OPERATOR_GTE.equals(operator))
				{
					predicate = cb.ge(propertyExpression, obtainNumber(value, beanClass, property));
				}
				else if (PersistenceCriterion.OPERATOR_LT.equals(operator))
				{
					predicate = cb.lt(propertyExpression, obtainNumber(value, beanClass, property));
				}
				else if (PersistenceCriterion.OPERATOR_LTE.equals(operator))
				{
					predicate = cb.le(propertyExpression, obtainNumber(value, beanClass, property));
				}
				else if (PersistenceCriterion.OPERATOR_LIKE.equals(operator))
				{
					predicate = cb.like((Expression) propertyExpression, (String) value);
				}
				else if (PersistenceCriterion.OPERATOR_NULL.equals(operator))
				{
					predicate = cb.isNull(propertyExpression);
				}

				cq.where(predicate);
			}

			// Run query and wrap result list into a collection that calls onLoad for each element that is being accessed.
			Query typeQuery = getEntityManager().createQuery(cq);
			if (query.getMaxResults() > 0)
			{
				typeQuery.setMaxResults(query.getMaxResults());
			}

			List result = typeQuery.getResultList();
			return result.iterator();
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	private Path determinePropertyPath(final String property, final Root entity)
	{
		Path x = null;
		StringTokenizer st = new StringTokenizer(property);
		while (st.hasMoreTokens())
		{
			String p = st.nextToken();
			if (x == null)
				x = entity.get(p);
			else
				x = x.get(p);
		}
		return x;
	}

	private Number obtainNumber(final Object value, final Class beanClass, final String propertyName)
	{
		if (value instanceof Number)
			return (Number) value;
		throw new PersistenceException("Expected numeric value as criteria of property '" + propertyName
			+ "' of entity '" + beanClass.getName() + "', got '" + value + "'.");
	}

	//////////////////////////////////////////////////
	// @@ Object modification
	//////////////////////////////////////////////////

	/**
	 * Saves the given object to the persistent storage.
	 * According to the persistence state of the object, performs an update or insert.
	 *
	 * @param o Object to insert or update
	 * @return The updated object; usually this will be identical to the argument object,
	 * except if the object already exists in persistent storage and a merge operation needs to be performed.
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Object saveObject(final Object o)
		throws PersistenceException
	{
		try
		{
			if (! getEntityManager().contains(o))
			{
				getEntityManager().persist(o);
			}
			return o;
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	/**
	 * Deletes an object from persistent storage.
	 *
	 * @param o Object to delete
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public void deleteObject(final Object o)
		throws PersistenceException
	{
		try
		{
			getEntityManager().remove(o);
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	//////////////////////////////////////////////////
	// @@ SQL support
	//////////////////////////////////////////////////

	/**
	 * Runs the given SQL update or delete statement.
	 *
	 * @param sql An SQL update statement
	 * @return The number of rows affected.
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public int executeUpdateOrDelete(final String sql)
		throws PersistenceException
	{
		try
		{
			Query query = getEntityManager().createNativeQuery(sql);
			int count = query.executeUpdate();
			return count;
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	/**
	 * Runs the given SQL select statement.
	 *
	 * @param sql SQL query to run
	 * @param maxResults Maximum number of result rows or 0 for unlimited
	 * @return A list of result elements (contains Object or Object[] elements, depending if this was a single column or multi-column query)
	 * @throws PersistenceException On error
	 */
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	@Override
	public Iterator executeSelect(final String sql, final int maxResults)
		throws PersistenceException
	{
		try
		{
			Query query = getEntityManager().createQuery(sql);
			if (maxResults > 0)
			{
				query.setMaxResults(maxResults);
			}

			List result = query.getResultList();
			return result.iterator();
		}
		catch (Exception e)
		{
			throw createLoggedException(e);
		}
	}

	//////////////////////////////////////////////////
	// @@ Transaction control
	//////////////////////////////////////////////////

	/** Thread local that holds the persistence context bound to this thread */
	protected ThreadLocal threadTransaction = new ThreadLocal();

	@Override
	public boolean isTransactionActive()
		throws PersistenceException
	{
		if (springTransactionMgr != null)
		{
			TransactionStatus ta = (TransactionStatus) threadTransaction.get();
			if (ta == null)
				return false;
			return true;
		}
		return getTransaction().isActive();
	}

	@Override
	public void beginTransaction()
		throws PersistenceException
	{
		if (isTransactional())
		{
			doBeginTransaction();
		}
	}

	@Override
	public void rollbackTransaction()
		throws PersistenceException
	{
		if (isTransactional())
		{
			doRollbackTransaction();
		}
		else
		{
			if (springTransactionMgr != null)
			{
				TransactionStatus ta = (TransactionStatus) threadTransaction.get();
				if (ta != null)
				{
					ta.setRollbackOnly();
					threadTransaction.set(null);
					LogUtil.trace(getClass(), "Set transaction to rollback only.");
				}
			}
			else
			{
				getTransaction().setRollbackOnly();
			}
		}
	}

	@Override
	public void commitTransaction()
		throws PersistenceException
	{
		if (isTransactional())
		{
			doCommitTransaction();
		}
	}

	@Override
	public void doBeginTransaction()
		throws PersistenceException
	{
		if (springTransactionMgr != null)
		{
			TransactionStatus ta = (TransactionStatus) threadTransaction.get();
			if (ta == null)
			{
				ta = springTransactionMgr.getTransaction(transactionDefinition);
				threadTransaction.set(ta);
			}
		}
		else
		{
			if (! getTransaction().isActive())
			{
				LogUtil.trace(getClass(), "Beginning transaction.");
				getTransaction().begin();
			}
		}
	}

	@Override
	public void doRollbackTransaction()
		throws PersistenceException
	{
		if (springTransactionMgr != null)
		{
			getEntityManager().flush();
			TransactionStatus ta = (TransactionStatus) threadTransaction.get();
			if (ta != null)
			{
				springTransactionMgr.rollback(ta);
				threadTransaction.set(null);
				LogUtil.trace(getClass(), "Rolled back transaction.");
			}
		}
		else
		{
			if (getTransaction().isActive())
			{
				LogUtil.trace(getClass(), "Rolling back transaction on.");

				try
				{
					getTransaction().rollback();
				}
				catch (Exception e)
				{
					throw createLoggedException(e);
				}
			}
		}
	}

	@Override
	public void doCommitTransaction()
		throws PersistenceException
	{
		if (springTransactionMgr != null)
		{
			// getEntityManager().flush();
			TransactionStatus ta = (TransactionStatus) threadTransaction.get();
			if (ta != null)
			{
				springTransactionMgr.commit(ta);
				threadTransaction.set(null);
				LogUtil.trace(getClass(), "Committed transaction.");
			}
		}
		else
		{
			if (getTransaction().isActive())
			{
				LogUtil.trace(getClass(), "Committing transaction.");

				try
				{
					getEntityManager().flush();
					getTransaction().commit();
					LogUtil.trace(getClass(), "Committed transaction.");
				}
				catch (Exception e)
				{
					throw createLoggedException(e);
				}
			}
		}
	}

	@Override
	public void flush()
		throws PersistenceException
	{
		if (supportsTransactions() && getTransaction().isActive())
		{
			try
			{
				getEntityManager().flush();
			}
			catch (Exception e)
			{
				throw createLoggedException(e);
			}
		}
	}

	protected boolean supportsTransactions()
	{
		try
		{
			getEntityManager().getTransaction();
		}
		catch (IllegalStateException e)
		{
			return false;
		}
		return true;
	}

	//////////////////////////////////////////////////
	// @@ JPA-specific
	//////////////////////////////////////////////////

	/**
	 * Gets the underlying JPA entity manager.
	 */
	public EntityManager getEntityManager()
	{
		if (entityManager != null)
		{
			if (! entityManager.isOpen())
			{
				LogUtil.debug(getClass(), "JPA session $0 was closed, going to open a new session.", entityManager);
				entityManager = null;
			}
		}

		if (entityManager == null)
		{
			if (entityManagerFactory == null)
			{
				// Create an entity manager factory if nothing has been provided externally
				entityManagerFactory = Persistence.createEntityManagerFactory(persistentUnitName);
			}
			entityManager = entityManagerFactory.createEntityManager();

			LogUtil.debug(getClass(), "Opened JPA session $0.", entityManager);
		}

		return entityManager;
	}

	private EntityTransaction getTransaction()
	{
		return getEntityManager() != null ? getEntityManager().getTransaction() : null;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy