Java tutorial
/* * Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved. * * This file is part of the Jspresso framework. * * Jspresso is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Jspresso is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Jspresso. If not, see <http://www.gnu.org/licenses/>. */ package org.jspresso.framework.application.backend.persistence.hibernate; import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.sql.DataSource; import org.hibernate.Criteria; import org.hibernate.Filter; import org.hibernate.FlushMode; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.NonUniqueObjectException; import org.hibernate.ObjectNotFoundException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.collection.internal.AbstractPersistentCollection; import org.hibernate.collection.internal.PersistentList; import org.hibernate.collection.internal.PersistentSet; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.jspresso.framework.application.backend.AbstractBackendController; import org.jspresso.framework.application.backend.BackendException; import org.jspresso.framework.application.backend.session.EMergeMode; import org.jspresso.framework.model.component.IComponent; import org.jspresso.framework.model.descriptor.ICollectionPropertyDescriptor; import org.jspresso.framework.model.descriptor.IComponentDescriptor; import org.jspresso.framework.model.descriptor.IPropertyDescriptor; import org.jspresso.framework.model.descriptor.IRelationshipEndPropertyDescriptor; import org.jspresso.framework.model.entity.CarbonEntityCloneFactory; import org.jspresso.framework.model.entity.IEntity; import org.jspresso.framework.model.entity.IEntityFactory; import org.jspresso.framework.model.entity.IEntityRegistry; import org.jspresso.framework.model.persistence.hibernate.entity.HibernateEntityRegistry; import org.jspresso.framework.util.bean.IPropertyChangeCapable; /** * This is the default Jspresso implementation of Hibernate-based backend * controller. * * @author Vincent Vandenschrick */ public class HibernateBackendController extends AbstractBackendController { private SessionFactory hibernateSessionFactory; private FlushMode defaultTxFlushMode = FlushMode.COMMIT; private DataSource noTxDataSource; /** * {@code JSPRESSO_SESSION_GLOBALS} is "JspressoSessionGlobals". */ public static final String JSPRESSO_SESSION_GLOBALS = "JspressoSessionGlobals"; /** * {@code JSPRESSO_SESSION_GLOBALS_LOGIN} is "login". */ public static final String JSPRESSO_SESSION_GLOBALS_LOGIN = "login"; /** * {@code JSPRESSO_SESSION_GLOBALS_LANGUAGE} is "language". */ public static final String JSPRESSO_SESSION_GLOBALS_LANGUAGE = "language"; private static final Logger LOG = LoggerFactory.getLogger(HibernateBackendController.class); /** * {@inheritDoc} */ @Override public <E extends IEntity> List<E> cloneInUnitOfWork(List<E> entities) { IEntityRegistry uowEntityRegistry = getUowEntityRegistry(); final List<E> uowEntities = super.cloneInUnitOfWork(entities); IEntityRegistry alreadyDetached = createEntityRegistry("detachFromHibernateInDepth"); IEntityRegistry alreadyLocked = createEntityRegistry("lockInHibernateInDepth"); for (IEntity uowEntity : uowEntities) { // prevent detach/attach entities that were previously in the UOW for performance reasons. // see bug jspresso-ce-#103 if (uowEntity != null && uowEntityRegistry.get(getComponentContract(uowEntity), uowEntity.getId()) == null) { detachFromHibernateInDepth(uowEntity, getHibernateSession(), alreadyDetached); lockInHibernateInDepth(uowEntity, getHibernateSession(), alreadyLocked); } } return uowEntities; } /** * Look-ups the entity in the session 1st. If it is there, return it so that * it avoids an unnecessary deep carbon copy. * * @param <E> * the actual entity type. * @param entity * the source entity. * @return the cloned entity. */ @SuppressWarnings("unchecked") @Override protected <E extends IEntity> E performUowEntityCloning(final E entity) { if (!isInitialized(entity) || entity.isPersistent()) { E sessionEntity; if (getHibernateSession().contains(entity)) { sessionEntity = entity; } else { sessionEntity = (E) getHibernateSession().load(getComponentContract(entity), entity.getId()); if (!isInitialized(entity)) { return sessionEntity; } if (!isInitialized(sessionEntity)) { getHibernateSession().evict(sessionEntity); sessionEntity = null; } } if (sessionEntity != null) { CarbonEntityCloneFactory.carbonCopyComponent(entity, sessionEntity, getEntityFactory()); return sessionEntity; } } // fall-back to default cloning. return super.performUowEntityCloning(entity); } /** * {@inheritDoc} */ @Override public void doBeginUnitOfWork() { // This is to avoid having entities attached to 2 open sessions // and to periodically clear the noTxSession cache. if (noTxSession != null) { noTxSession.clear(); } super.doBeginUnitOfWork(); } private Session noTxSession = null; /** * Retrieves the current thread-bound / tx-bound Hibernate session. this is * now the preferred way to perform Hibernate operations. It relies on the * Hibernate 3.1+ {@link SessionFactory#getCurrentSession()} method. * * @return the current thread-bound / tx-bound Hibernate session. */ public Session getHibernateSession() { Session currentSession; if (isUnitOfWorkActive()) { try { currentSession = getHibernateSessionFactory().getCurrentSession(); } catch (HibernateException ex) { currentSession = getNoTxSession(); } } else { currentSession = getNoTxSession(); } if (currentSession != noTxSession) { // we are on a transactional session. currentSession.setFlushMode(getDefaultTxFlushMode()); } configureHibernateGlobalFilter(currentSession.enableFilter(JSPRESSO_SESSION_GLOBALS)); return currentSession; } private Session getNoTxSession() { if (noTxSession == null) { if (noTxDataSource != null) { try { noTxSession = getHibernateSessionFactory().withOptions() .connection(noTxDataSource.getConnection()).openSession(); } catch (SQLException ex) { LOG.error("Couldn't get connection from non transactional data source {}", noTxDataSource); throw new BackendException(ex, "Couldn't get connection from non transactional data source"); } } else { noTxSession = getHibernateSessionFactory().openSession(); } noTxSession.setFlushMode(FlushMode.MANUAL); } return noTxSession; } private void configureHibernateGlobalFilter(Filter filter) { String filterLanguage = null; if (getLocale() != null) { filterLanguage = getLocale().getLanguage(); } if (filterLanguage == null) { filterLanguage = ""; } String filterLogin = null; if (getApplicationSession().getPrincipal() != null) { filterLogin = getApplicationSession().getUsername(); } if (filterLogin == null) { filterLogin = ""; } filter.setParameter(JSPRESSO_SESSION_GLOBALS_LANGUAGE, filterLanguage); filter.setParameter(JSPRESSO_SESSION_GLOBALS_LOGIN, filterLogin); } private Session currentInitializationSession = null; /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public void initializePropertyIfNeeded(final IComponent componentOrEntity, final String propertyName) { Object propertyValue = componentOrEntity.straightGetProperty(propertyName); if (!isInitialized(propertyValue)) { // turn off dirt tracking. boolean dirtRecorderWasEnabled = isDirtyTrackingEnabled(); try { setDirtyTrackingEnabled(false); boolean isSessionEntity = isSessionEntity(componentOrEntity); boolean isUnitOfWorkActive = isUnitOfWorkActive(); // Never initialize session entities from UOW session if (!(isUnitOfWorkActive && isSessionEntity)) { if (propertyValue instanceof Collection<?> && propertyValue instanceof AbstractPersistentCollection) { if (((AbstractPersistentCollection) propertyValue).getSession() != null && ((AbstractPersistentCollection) propertyValue).getSession().isOpen()) { try { Hibernate.initialize(propertyValue); relinkAfterInitialization((Collection<?>) propertyValue, componentOrEntity); } catch (Exception ex) { LOG.error("An internal error occurred when forcing {} collection initialization.", propertyName); LOG.error("Source exception", ex); } } } else if (propertyValue instanceof HibernateProxy) { HibernateProxy proxy = (HibernateProxy) propertyValue; LazyInitializer li = proxy.getHibernateLazyInitializer(); if (li.getSession() != null && li.getSession().isOpen()) { try { Hibernate.initialize(propertyValue); } catch (Exception ex) { LOG.error("An internal error occurred when forcing {} reference initialization.", propertyName); LOG.error("Source exception", ex); } } } } if (!isInitialized(propertyValue)) { if (currentInitializationSession != null) { performPropertyInitializationUsingSession(componentOrEntity, propertyName, currentInitializationSession); } else { boolean suspendUnitOfWork = false; boolean startUnitOfWork = false; if (isSessionEntity) { // Always use NoTxSession to initialize session entities if (isUnitOfWorkActive) { suspendUnitOfWork = true; } } else { if (!isUnitOfWorkActive) { // Never use NoTxSession to initialize non session entities (see bug #1153) startUnitOfWork = true; } } try { if (suspendUnitOfWork) { suspendUnitOfWork(); } if (startUnitOfWork) { getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { initializeProperty(componentOrEntity, propertyName); status.setRollbackOnly(); } }); } else { initializeProperty(componentOrEntity, propertyName); } } finally { if (suspendUnitOfWork) { resumeUnitOfWork(); } } } } componentOrEntity.firePropertyChange(propertyName, IPropertyChangeCapable.UNKNOWN, propertyValue); } finally { setDirtyTrackingEnabled(dirtRecorderWasEnabled); } } } private void initializeProperty(IComponent componentOrEntity, String propertyName) { Session hibernateSession = getHibernateSession(); FlushMode oldFlushMode = hibernateSession.getFlushMode(); try { // Temporary switch to a read-only session. hibernateSession.setFlushMode(FlushMode.MANUAL); try { currentInitializationSession = hibernateSession; performPropertyInitializationUsingSession(componentOrEntity, propertyName, hibernateSession); } finally { currentInitializationSession = null; } } finally { hibernateSession.setFlushMode(oldFlushMode); } } private boolean isSessionEntity(IComponent componentOrEntity) { return componentOrEntity instanceof IEntity && getRegisteredEntity(((IEntity) componentOrEntity).getComponentContract(), ((IEntity) componentOrEntity).getId()) == componentOrEntity; } @SuppressWarnings("unchecked") private void performPropertyInitializationUsingSession(final IComponent componentOrEntity, final String propertyName, Session hibernateSession) { Object propertyValue = componentOrEntity.straightGetProperty(propertyName); if (!isInitialized(propertyValue)) { IEntityRegistry alreadyDetached = createEntityRegistry("detachFromHibernateInDepth"); if (componentOrEntity instanceof IEntity) { if (((IEntity) componentOrEntity).isPersistent()) { detachFromHibernateInDepth(componentOrEntity, hibernateSession, alreadyDetached); lockInHibernate((IEntity) componentOrEntity, hibernateSession); } else if (IEntity.DELETED_VERSION.equals(((IEntity) componentOrEntity).getVersion())) { LOG.error("Trying to initialize a property ({}) on a deleted object ({} : {}).", propertyName, componentOrEntity.getComponentContract().getName(), componentOrEntity); throw new RuntimeException("Trying to initialize a property (" + propertyName + ") on a deleted object (" + componentOrEntity.getComponentContract().getName() + " : " + componentOrEntity + ")"); } else if (propertyValue instanceof IEntity) { detachFromHibernateInDepth((IEntity) propertyValue, hibernateSession, alreadyDetached); lockInHibernate((IEntity) propertyValue, hibernateSession); } } else if (propertyValue instanceof IEntity) { // to handle initialization of component properties. detachFromHibernateInDepth((IEntity) propertyValue, hibernateSession, alreadyDetached); lockInHibernate((IEntity) propertyValue, hibernateSession); } if (propertyValue instanceof HibernateProxy) { HibernateProxy proxy = (HibernateProxy) propertyValue; LazyInitializer li = proxy.getHibernateLazyInitializer(); if (li.getSession() == null) { try { li.setSession((SessionImplementor) hibernateSession); } catch (Exception ex) { LOG.error( "An internal error occurred when re-associating Hibernate session for {} reference initialization.", propertyName); LOG.error("Source exception", ex); } } } else if (propertyValue instanceof AbstractPersistentCollection) { AbstractPersistentCollection apc = (AbstractPersistentCollection) propertyValue; if (apc.getSession() == null) { try { apc.setCurrentSession((SessionImplementor) hibernateSession); } catch (Exception ex) { LOG.error( "An internal error occurred when re-associating Hibernate session for {} reference initialization.", propertyName); LOG.error("Source exception", ex); } } } Hibernate.initialize(propertyValue); if (propertyValue instanceof Collection<?> && propertyValue instanceof PersistentCollection) { relinkAfterInitialization((Collection<?>) propertyValue, componentOrEntity); for (Iterator<?> ite = ((Collection<?>) propertyValue).iterator(); ite.hasNext();) { Object collectionElement = ite.next(); if (collectionElement instanceof IEntity) { if (isEntityRegisteredForDeletion((IEntity) collectionElement)) { ite.remove(); } } } } else { relinkAfterInitialization(Collections.singleton(propertyValue), componentOrEntity); } clearPropertyDirtyState(propertyValue); } } private void relinkAfterInitialization(Collection<?> elements, Object owner) { for (Object element : elements) { // Should always be the case but there might be problems with lists // containing holes. if (element instanceof IComponent) { for (Map.Entry<String, Object> property : ((IComponent) element).straightGetProperties() .entrySet()) { if (property.getValue() instanceof IEntity && owner instanceof IEntity) { if (owner != property.getValue() // avoid lazy initialization && ((IEntity) owner).getId().equals(((IEntity) property.getValue()).getId()) // To avoid bug #548 && Hibernate.getClass(owner) == Hibernate.getClass(property.getValue())) { ((IComponent) element).straightSetProperty(property.getKey(), owner); } } } } } } /** * {@inheritDoc} */ @Override public boolean isInitialized(Object objectOrProxy) { return Hibernate.isInitialized(objectOrProxy); } /** * {@inheritDoc} */ @Override public void registerForDeletion(IEntity entity) { super.registerForDeletion(entity); if (isUnitOfWorkActive()) { Session hibernateSession = getHibernateSession(); if (hibernateSession != getNoTxSession()) { // we are in an real tx hibernateSession.delete(entity); } } } /** * {@inheritDoc} */ @Override public void registerForUpdate(IEntity entity) { super.registerForUpdate(entity); if (isUnitOfWorkActive()) { Session hibernateSession = getHibernateSession(); if (hibernateSession != getNoTxSession()) { // we are in an real tx hibernateSession.saveOrUpdate(entity); } } } /** * This method wraps transient collections in the equivalent hibernate ones. * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override protected <E> Collection<E> wrapDetachedCollection(IEntity owner, Collection<E> detachedCollection, Collection<E> snapshotCollection, String role) { Collection<E> varSnapshotCollection = snapshotCollection; if (!(detachedCollection instanceof PersistentCollection)) { String collectionRoleName = HibernateHelper.getHibernateRoleName(getComponentContract(owner), role); if (collectionRoleName == null) { // it is not an hibernate managed collection (e.g. "detachedEntities") return detachedCollection; } if (detachedCollection instanceof Set) { PersistentSet persistentSet = new PersistentSet(null, (Set<?>) detachedCollection); changeCollectionOwner(persistentSet, owner); HashMap<Object, Object> snapshot = new HashMap<>(); if (varSnapshotCollection == null) { persistentSet.clearDirty(); varSnapshotCollection = detachedCollection; } for (Object snapshotCollectionElement : varSnapshotCollection) { snapshot.put(snapshotCollectionElement, snapshotCollectionElement); } persistentSet.setSnapshot(owner.getId(), collectionRoleName, snapshot); return persistentSet; } else if (detachedCollection instanceof List) { PersistentList persistentList = new PersistentList(null, (List<?>) detachedCollection); changeCollectionOwner(persistentList, owner); ArrayList<Object> snapshot = new ArrayList<>(); if (varSnapshotCollection == null) { persistentList.clearDirty(); varSnapshotCollection = detachedCollection; } for (Object snapshotCollectionElement : varSnapshotCollection) { snapshot.add(snapshotCollectionElement); } persistentList.setSnapshot(owner.getId(), collectionRoleName, snapshot); return persistentList; } } else { if (varSnapshotCollection == null) { ((PersistentCollection) detachedCollection).clearDirty(); } else { ((PersistentCollection) detachedCollection).dirty(); } } return detachedCollection; } /** * Merge non initialized entity. * * @param <E> * the actual entity type * @param entity * the entity * @return the merged entity */ @SuppressWarnings("unchecked") @Override protected <E extends IEntity> E mergeUninitializedEntity(E entity) { return (E) getNoTxSession().load(getComponentContract(entity), entity.getId()); } /** * Merge collection. * * @param <E> * the actual entity type. * @param propertyName * the property name * @param propertyValue * the property value * @param registeredEntity * the registered entity * @param registeredCollection * the registered collection * @return the collection */ @Override @SuppressWarnings("unchecked") protected <E extends IEntity> Collection<?> mergeCollection(String propertyName, Object propertyValue, E registeredEntity, Collection<?> registeredCollection) { Collection<?> mergedCollection; if (propertyValue instanceof PersistentCollection) { Collection<?> snapshotCollection = null; Map<String, Object> dirtyProperties = getDirtyProperties(registeredEntity); if (dirtyProperties != null) { Object originalProperty = dirtyProperties.get(propertyName); // Workaround bug #1148 if (originalProperty != null && originalProperty instanceof Collection<?>) { snapshotCollection = (Collection<?>) originalProperty; } } mergedCollection = wrapDetachedCollection(registeredEntity, ((Collection<Object>) registeredCollection), ((Collection<Object>) snapshotCollection), propertyName); } else { mergedCollection = registeredCollection; } return mergedCollection; } /** * Locks an entity (LockMode.NONE) in current hibernate session. * * @param entity * the entity to lock. * @param hibernateSession * the hibernate session. */ private void lockInHibernate(IEntity entity, Session hibernateSession) { if (!hibernateSession.contains(entity)) { // Do not use get before trying to lock. // Get performs a DB query. try { hibernateSession.buildLockRequest(LockOptions.NONE).lock(entity); } catch (NonUniqueObjectException ex) { if (hibernateSession == noTxSession) { hibernateSession.clear(); hibernateSession.buildLockRequest(LockOptions.NONE).lock(entity); } else { throw ex; } } } } @SuppressWarnings("unchecked") private void lockInHibernateInDepth(IComponent component, Session hibernateSession, IEntityRegistry alreadyLocked) { if (component == null) { return; } if (!isInitialized(component)) { lockInHibernate((IEntity) component, hibernateSession); return; } boolean isEntity = component instanceof IEntity; if (!isEntity || alreadyLocked.get(getComponentContract((IEntity) component), ((IEntity) component).getId()) == null) { if (isEntity) { alreadyLocked.register(getComponentContract((IEntity) component), ((IEntity) component).getId(), (IEntity) component); if (((IEntity) component).isPersistent()) { lockInHibernate((IEntity) component, hibernateSession); } // else { // Cannot simply re-attach the transient entity, so we have to // saveOrUpdate it. // This is a bad evolution since we don't know if we want to actually // create the new entity. If we want to delete it, all checks will be // triggered. // if (!isEntityRegisteredForDeletion((IEntity) component)) { // registerForUpdate((IEntity) component); // } // } } Map<String, Object> entityProperties = component.straightGetProperties(); IComponentDescriptor<?> componentDescriptor = getEntityFactory() .getComponentDescriptor(getComponentContract(component)); for (Map.Entry<String, Object> property : entityProperties.entrySet()) { String propertyName = property.getKey(); Object propertyValue = property.getValue(); IPropertyDescriptor propertyDescriptor = componentDescriptor.getPropertyDescriptor(propertyName); if (propertyValue instanceof IEntity) { lockInHibernateInDepth((IEntity) propertyValue, hibernateSession, alreadyLocked); } else if (propertyValue instanceof Collection && propertyDescriptor instanceof ICollectionPropertyDescriptor<?> && isInitialized(propertyValue)) { for (Object element : ((Collection<?>) property.getValue())) { if (element instanceof IComponent) { lockInHibernateInDepth((IComponent) element, hibernateSession, alreadyLocked); } } if (propertyValue instanceof PersistentCollection) { Collection<?> snapshot = null; Object storedSnapshot = ((PersistentCollection) propertyValue).getStoredSnapshot(); if (storedSnapshot instanceof Map<?, ?>) { snapshot = ((Map<IComponent, IComponent>) storedSnapshot).keySet(); } else if (storedSnapshot instanceof Collection<?>) { snapshot = (Collection<?>) storedSnapshot; } if (snapshot != null) { for (Object element : snapshot) { if (element instanceof IComponent) { lockInHibernateInDepth((IComponent) element, hibernateSession, alreadyLocked); } } } } } } } } @SuppressWarnings("unchecked") void detachFromHibernateInDepth(IComponent component, Session hibernateSession, IEntityRegistry alreadyDetached) { if (component == null) { return; } boolean isEntity = component instanceof IEntity; // Always detach from Hibernate session. We might have already traversed the entity but not its Hibernate proxy. if (isEntity) { HibernateHelper.unsetProxyHibernateSession((IEntity) component, hibernateSession); } if (!isEntity || alreadyDetached.get(getComponentContract((IEntity) component), ((IEntity) component).getId()) == null) { // Do not store uninitialized components since we may traverse initialized ones. if (isEntity && isInitialized(component)) { alreadyDetached.register(getComponentContract((IEntity) component), ((IEntity) component).getId(), (IEntity) component); } if (isInitialized(component)) { Map<String, Object> properties = component.straightGetProperties(); for (Map.Entry<String, Object> property : properties.entrySet()) { Object propertyValue = property.getValue(); if (propertyValue instanceof IComponent) { detachFromHibernateInDepth((IComponent) propertyValue, hibernateSession, alreadyDetached); } else if (propertyValue instanceof PersistentCollection) { HibernateHelper.unsetCollectionHibernateSession((PersistentCollection) propertyValue, hibernateSession); if (propertyValue instanceof Collection<?> && isInitialized(propertyValue)) { for (Object element : ((Collection<?>) propertyValue)) { if (element instanceof IComponent) { detachFromHibernateInDepth((IComponent) element, hibernateSession, alreadyDetached); } } } } } } } } /** * Finds an entity by ID. * * @param <T> * the entity type to return * @param id * the entity ID. * @param mergeMode * the merge mode to use when merging back retrieved entities or null * if no merge is requested. * @param clazz * the type of the entity. * @return the found entity */ @SuppressWarnings("unchecked") public <T extends IEntity> T findById(final Serializable id, final EMergeMode mergeMode, final Class<? extends T> clazz) { T res; if (isUnitOfWorkActive()) { // merge mode must be ignored if a transaction is pre-existing. res = cloneInUnitOfWork((T) getHibernateSession().get(clazz, id)); } else { // merge mode is used for merge to occur inside the transaction. res = getTransactionTemplate().execute(new TransactionCallback<T>() { @SuppressWarnings("unchecked") @Override public T doInTransaction(TransactionStatus status) { return merge((T) getHibernateSession().get(clazz, id), mergeMode); } }); } return res; } /** * Search Hibernate using criteria. The result is then merged into session unless the method is called into a * pre-existing transaction, in which case, the merge mode is ignored and the merge is not performed. * * @param <T> * the entity type to return * @param criteria * the detached criteria. * @param mergeMode * the merge mode to use when merging back retrieved entities or null * if no merge is requested. * @param clazz * the type of the entity. * @return the first found entity or null */ public <T extends IEntity> T findFirstByCriteria(DetachedCriteria criteria, EMergeMode mergeMode, Class<? extends T> clazz) { List<T> ret = findByCriteria(criteria, 0, 1, mergeMode, clazz); if (ret != null && !ret.isEmpty()) { return ret.get(0); } return null; } /** * Search Hibernate using criteria. The result is then merged into session unless the method is called into a * pre-existing transaction, in which case, the merge mode is ignored and the merge is not performed. * * @param <T> * the entity type to return * @param criteria * the detached criteria. * @param mergeMode * the merge mode to use when merging back retrieved entities or null * if no merge is requested. * @param clazz * the type of the entity. * @return the first found entity or null */ public <T extends IEntity> List<T> findByCriteria(final DetachedCriteria criteria, EMergeMode mergeMode, Class<? extends T> clazz) { return findByCriteria(criteria, -1, -1, mergeMode, clazz); } /** * Search Hibernate using criteria. The result is then merged into session unless the method is called into a * pre-existing transaction, in which case, the merge mode is ignored and the merge is not performed. * * @param <T> * the entity type to return * @param criteria * the detached criteria. * @param firstResult * the first result rank to retrieve. * @param maxResults * the max number of results to retrieve. * @param mergeMode * the merge mode to use when merging back retrieved entities or null * if no merge is requested. * @param clazz * the type of the entity. * @return the first found entity or null */ @SuppressWarnings("UnusedParameters") public <T extends IEntity> List<T> findByCriteria(final DetachedCriteria criteria, int firstResult, int maxResults, EMergeMode mergeMode, Class<? extends T> clazz) { List<T> res; if (isUnitOfWorkActive()) { // merge mode must be ignored if a transaction is pre-existing, so force // to null. // This is useless to clone in UOW now that UOW registration is done // in onLoad interceptor // res = (List<T>) cloneInUnitOfWork(find(criteria, firstResult, // maxResults, null)); res = find(criteria, firstResult, maxResults, null); } else { // merge mode is passed for merge to occur inside the transaction. res = find(criteria, firstResult, maxResults, mergeMode); } return res; } private <T extends IEntity> List<T> find(final DetachedCriteria criteria, final int firstResult, final int maxResults, final EMergeMode mergeMode) { List<T> entities = getTransactionTemplate().execute(new TransactionCallback<List<T>>() { @SuppressWarnings("unchecked") @Override public List<T> doInTransaction(TransactionStatus status) { Criteria executableCriteria = criteria.getExecutableCriteria(getHibernateSession()); if (firstResult >= 0) { executableCriteria.setFirstResult(firstResult); } if (maxResults > 0) { executableCriteria.setMaxResults(maxResults); } List<T> entities = executableCriteria.list(); if (mergeMode != null) { entities = merge(entities, mergeMode); } return entities; } }); if (isUnitOfWorkActive()) { entities = cloneInUnitOfWork(entities); } return entities; } /** * Unwrap hibernate proxy if needed. * * @param componentOrProxy * the component or proxy. * @return the proxy implementation if it's an hibernate proxy. */ @Override protected Object unwrapProxy(Object componentOrProxy) { Object component; if (componentOrProxy instanceof HibernateProxy) { // we must unwrap the proxy to avoid class cast exceptions. // see // http://forum.hibernate.org/viewtopic.php?p=2323464&sid=cb4ba3a4418276e5d2fbdd6c906ba734 component = ((HibernateProxy) componentOrProxy).getHibernateLazyInitializer().getImplementation(); } else { component = componentOrProxy; } return component; } /** * Reloads an entity in Hibernate. * * @param entity * the entity to reload. */ @Override public void reload(final IEntity entity) { if (entity == null) { throw new IllegalArgumentException("Passed entity cannot be null"); } // builds a collection of entities to reload. Set<IEntity> dirtyReachableEntities = buildReachableDirtyEntitySet(entity); if (entity.isPersistent()) { getTransactionTemplate().execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { Exception deletedObjectEx = null; try { merge((IEntity) getHibernateSession().load(getComponentContract(entity).getName(), entity.getId()), EMergeMode.MERGE_CLEAN_EAGER); } catch (ObjectNotFoundException ex) { deletedObjectEx = ex; } // if reloadObject is part of a pre-existing transaction, do not rollback the TX // status.setRollbackOnly(); if (deletedObjectEx != null) { throw new ConcurrencyFailureException(deletedObjectEx.getMessage(), deletedObjectEx); } } }); } // traverse the reachable dirty entities to explicitly reload the // ones that were not reloaded by the previous pass. for (IEntity reachableEntity : dirtyReachableEntities) { if (reachableEntity.isPersistent() && isDirty(reachableEntity)) { reload(reachableEntity); } } } private Set<IEntity> buildReachableDirtyEntitySet(IEntity entity) { Set<IEntity> reachableDirtyEntities = new HashSet<>(); completeReachableDirtyEntities(entity, reachableDirtyEntities, new HashSet<IEntity>()); return reachableDirtyEntities; } private void completeReachableDirtyEntities(IEntity entity, Set<IEntity> reachableDirtyEntities, Set<IEntity> alreadyTraversed) { if (alreadyTraversed.contains(entity)) { return; } alreadyTraversed.add(entity); if (isDirty(entity)) { reachableDirtyEntities.add(entity); } Map<String, Object> entityProps = entity.straightGetProperties(); IComponentDescriptor<?> entityDescriptor = getEntityFactory() .getComponentDescriptor(getComponentContract(entity)); for (Map.Entry<String, Object> property : entityProps.entrySet()) { Object propertyValue = property.getValue(); if (propertyValue instanceof IEntity) { IPropertyDescriptor propertyDescriptor = entityDescriptor.getPropertyDescriptor(property.getKey()); if (isInitialized(propertyValue) && propertyDescriptor instanceof IRelationshipEndPropertyDescriptor // It's not a master data relationship. && ((IRelationshipEndPropertyDescriptor) propertyDescriptor) .getReverseRelationEnd() != null) { completeReachableDirtyEntities((IEntity) propertyValue, reachableDirtyEntities, alreadyTraversed); } } else if (propertyValue instanceof Collection<?>) { if (isInitialized(propertyValue)) { for (Object elt : ((Collection<?>) propertyValue)) { if (elt instanceof IEntity) { completeReachableDirtyEntities((IEntity) elt, reachableDirtyEntities, alreadyTraversed); } } } } } } private void changeCollectionOwner(Collection<?> persistentCollection, Object newOwner) { if (persistentCollection instanceof PersistentCollection) { ((PersistentCollection) persistentCollection).setOwner(newOwner); } } /** * Hibernate related cloning. * <p/> * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override protected <E> E cloneUninitializedProperty(Object owner, E propertyValue) { E clonedPropertyValue = propertyValue; if (isInitialized(owner)) { if (propertyValue instanceof PersistentCollection) { if (unwrapProxy((((PersistentCollection) propertyValue).getOwner())) != unwrapProxy(owner)) { if (propertyValue instanceof PersistentSet) { clonedPropertyValue = (E) new PersistentSet( // Must reset the session. // See bug #902 /* ((PersistentSet) propertyValue).getSession() */null); } else if (propertyValue instanceof PersistentList) { clonedPropertyValue = (E) new PersistentList( // Must reset the session. // See bug #902 /* ((PersistentList) propertyValue).getSession() */null); } changeCollectionOwner((Collection<?>) clonedPropertyValue, owner); ((PersistentCollection) clonedPropertyValue).setSnapshot( ((PersistentCollection) propertyValue).getKey(), ((PersistentCollection) propertyValue).getRole(), null); } } else { if (propertyValue instanceof HibernateProxy) { return (E) getHibernateSession().load( ((HibernateProxy) propertyValue).getHibernateLazyInitializer().getEntityName(), ((IEntity) propertyValue).getId()); } } } return clonedPropertyValue; } /** * Clears dirty state of persistent collections. * <p/> * {@inheritDoc} */ @Override protected void clearPropertyDirtyState(Object property) { if (property instanceof PersistentCollection) { ((PersistentCollection) property).clearDirty(); } } /** * Sets the hibernateSessionFactory. * * @param hibernateSessionFactory * the hibernateSessionFactory to set. */ public void setHibernateSessionFactory(SessionFactory hibernateSessionFactory) { this.hibernateSessionFactory = hibernateSessionFactory; } /** * Gets the hibernateSessionFactory. * * @return the hibernateSessionFactory. */ protected SessionFactory getHibernateSessionFactory() { return hibernateSessionFactory; } /** * Gets the defaultTxFlushMode. * * @return the defaultTxFlushMode. */ protected FlushMode getDefaultTxFlushMode() { return defaultTxFlushMode; } /** * Sets the default Hibernate flush mode to apply to the Hibernate session * when it is bound to a transaction. Defaults to {@link FlushMode#COMMIT}. * * @param defaultTxFlushMode * the defaultTxFlushMode to set. */ public void setDefaultTxFlushMode(String defaultTxFlushMode) { this.defaultTxFlushMode = FlushMode.valueOf(defaultTxFlushMode); } /** * {@inheritDoc} */ @Override public void cleanupRequestResources() { super.cleanupRequestResources(); if (noTxSession != null) { if (noTxSession.isOpen()) { Connection conn = noTxSession.close(); if (conn != null) { try { conn.close(); } catch (SQLException ex) { LOG.warn("The provided non transactional connection could not be correctly closed.", ex); } } } noTxSession = null; } } /** * Checks also for Hibernate proxies. * <p/> * {@inheritDoc} */ @Override protected boolean objectEquals(IEntity e1, IEntity e2) { return HibernateHelper.objectEquals(e1, e2); } /** * {@inheritDoc} */ @Override protected <E extends IComponent> Class<? extends E> getComponentContract(E component) { return HibernateHelper.getComponentContract(component); } /** * {@inheritDoc} */ @Override protected IEntityRegistry createEntityRegistry(String name, Map<Class<? extends IEntity>, Map<Serializable, IEntity>> backingStore) { return new HibernateEntityRegistry(name, backingStore); } /** * Configures the entity factory to use to create new entities. Backend * controllers only accept instances of * {@code HibernateControllerAwareProxyEntityFactory} or a subclass. * * @param entityFactory * the entityFactory to set. */ @Override public void setEntityFactory(IEntityFactory entityFactory) { if (!(entityFactory instanceof HibernateControllerAwareProxyEntityFactory)) { throw new IllegalArgumentException( "entityFactory must be a " + HibernateControllerAwareProxyEntityFactory.class.getSimpleName()); } super.setEntityFactory(entityFactory); } /** * Sets the noTxDataSource. * * @param noTxDataSource * the noTxDataSource to set. */ public void setNoTxDataSource(DataSource noTxDataSource) { this.noTxDataSource = noTxDataSource; } /** * Flushes the current underlying hibernate session. * <p/> * {@inheritDoc} */ @Override public void flush() { getHibernateSession().flush(); } /** * {@inheritDoc} */ @Override public void afterCompletion(int status) { super.afterCompletion(status); if (getTransactionTemplate().getTransactionManager() instanceof JtaTransactionManager) { TransactionSynchronizationManager.unbindResourceIfPossible(getHibernateSessionFactory()); } // This is to avoid having entities attached to 2 open sessions // and to periodically clear the noTxSession cache. if (noTxSession != null) { noTxSession.clear(); } } }