org.openbp.server.persistence.jpa.JpaPersistenceContextProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openbp-server-jpa Show documentation
Show all versions of openbp-server-jpa Show documentation
JPA-based persistence provider for the OpenBP process engine
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;
}
}