cleanup
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / dao / hibernate / common / CdmEntityDaoBase.java
index cafd6fc1961be7bc8625b3d2444b81a928d59363..981e1f408930c5a5ed1e568f7f01d69b8f27658f 100644 (file)
-/**\r
-* Copyright (C) 2007 EDIT\r
-* European Distributed Institute of Taxonomy\r
-* http://www.e-taxonomy.eu\r
-*\r
-* The contents of this file are subject to the Mozilla Public License Version 1.1\r
-* See LICENSE.TXT at the top of this package for the full license terms.\r
-*/\r
-\r
-package eu.etaxonomy.cdm.persistence.dao.hibernate.common;\r
-\r
-import java.lang.reflect.Field;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.UUID;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.hibernate.Criteria;\r
-import org.hibernate.FlushMode;\r
-import org.hibernate.HibernateException;\r
-import org.hibernate.LockOptions;\r
-import org.hibernate.NonUniqueObjectException;\r
-import org.hibernate.Query;\r
-import org.hibernate.Session;\r
-import org.hibernate.criterion.Criterion;\r
-import org.hibernate.criterion.DetachedCriteria;\r
-import org.hibernate.criterion.Example;\r
-import org.hibernate.criterion.Example.PropertySelector;\r
-import org.hibernate.criterion.Order;\r
-import org.hibernate.criterion.ProjectionList;\r
-import org.hibernate.criterion.Projections;\r
-import org.hibernate.criterion.Restrictions;\r
-import org.hibernate.envers.query.AuditQuery;\r
-import org.hibernate.metadata.ClassMetadata;\r
-import org.hibernate.type.Type;\r
-import org.joda.time.DateTime;\r
-import org.springframework.beans.factory.annotation.Autowired;\r
-import org.springframework.dao.DataAccessException;\r
-import org.springframework.dao.InvalidDataAccessApiUsageException;\r
-import org.springframework.security.core.Authentication;\r
-import org.springframework.security.core.context.SecurityContextHolder;\r
-import org.springframework.stereotype.Repository;\r
-import org.springframework.util.ReflectionUtils;\r
-\r
-import eu.etaxonomy.cdm.model.common.CdmBase;\r
-import eu.etaxonomy.cdm.model.common.IPublishable;\r
-import eu.etaxonomy.cdm.model.common.User;\r
-import eu.etaxonomy.cdm.model.common.VersionableEntity;\r
-import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;\r
-import eu.etaxonomy.cdm.persistence.dao.common.Restriction;\r
-import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;\r
-import eu.etaxonomy.cdm.persistence.dto.MergeResult;\r
-import eu.etaxonomy.cdm.persistence.hibernate.PostMergeEntityListener;\r
-import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadata;\r
-import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadataFactory;\r
-import eu.etaxonomy.cdm.persistence.query.Grouping;\r
-import eu.etaxonomy.cdm.persistence.query.MatchMode;\r
-import eu.etaxonomy.cdm.persistence.query.OrderHint;\r
-\r
-\r
-/**\r
- * @author a.mueller\r
- * FIXME CdmEntityDaoBase is abstract, can it be annotated with @Repository?\r
- */\r
-@Repository\r
-public abstract class CdmEntityDaoBase<T extends CdmBase>\r
-            extends DaoBase\r
-            implements ICdmEntityDao<T> {\r
-\r
-    private static final Logger logger = Logger.getLogger(CdmEntityDaoBase.class);\r
-\r
-    protected int flushAfterNo = 1000; //large numbers may cause synchronisation errors when commiting the session !!\r
-\r
-    protected Class<T> type;\r
-\r
-    @Autowired\r
-//     @Qualifier("defaultBeanInitializer")\r
-    protected IBeanInitializer defaultBeanInitializer;\r
-\r
-    public void setDefaultBeanInitializer(IBeanInitializer defaultBeanInitializer) {\r
-        this.defaultBeanInitializer = defaultBeanInitializer;\r
-    }\r
-\r
-    @Autowired\r
-    private ReferringObjectMetadataFactory referringObjectMetadataFactory;\r
-\r
-\r
-    public CdmEntityDaoBase(Class<T> type){\r
-        this.type = type;\r
-        logger.debug("Creating DAO of type [" + type.getSimpleName() + "]");\r
-    }\r
-\r
-    @Override\r
-    public void lock(T t, LockOptions lockOptions) {\r
-        getSession().buildLockRequest(lockOptions).lock(t);\r
-    }\r
-\r
-    @Override\r
-    public void refresh(T t, LockOptions lockOptions, List<String> propertyPaths) {\r
-        getSession().refresh(t, lockOptions);\r
-        defaultBeanInitializer.initialize(t, propertyPaths);\r
-    }\r
-\r
-    //TODO this method should be moved to a concrete class (not typed)\r
-    public UUID saveCdmObj(CdmBase cdmObj) throws DataAccessException  {\r
-        getSession().saveOrUpdate(cdmObj);\r
-        return cdmObj.getUuid();\r
-    }\r
-\r
-    //TODO: Replace saveCdmObj() by saveCdmObject_\r
-    private UUID saveCdmObject_(T cdmObj){\r
-        getSession().saveOrUpdate(cdmObj);\r
-        return cdmObj.getUuid();\r
-    }\r
-\r
-    //TODO: Use everywhere CdmEntityDaoBase.saveAll() instead of ServiceBase.saveCdmObjectAll()?\r
-    //TODO: why does this use saveCdmObject_ which actually savesOrUpdateds data ?\r
-    @Override\r
-    public Map<UUID, T> saveAll(Collection<T> cdmObjCollection){\r
-        int types = cdmObjCollection.getClass().getTypeParameters().length;\r
-        if (types > 0){\r
-            if (logger.isDebugEnabled()){logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);}\r
-        }\r
-\r
-        Map<UUID, T> resultMap = new HashMap<>();\r
-        Iterator<T> iterator = cdmObjCollection.iterator();\r
-        int i = 0;\r
-        while(iterator.hasNext()){\r
-            if ( ( (i % 2000) == 0) && (i > 0)   ){logger.debug("Saved " + i + " objects" );}\r
-            T cdmObj = iterator.next();\r
-            UUID uuid = saveCdmObject_(cdmObj);\r
-            if (logger.isDebugEnabled()){logger.debug("Save cdmObj: " + (cdmObj == null? null: cdmObj.toString()));}\r
-            resultMap.put(uuid, cdmObj);\r
-            i++;\r
-            if ( (i % flushAfterNo) == 0){\r
-                try{\r
-                    if (logger.isDebugEnabled()){logger.debug("flush");}\r
-                    flush();\r
-                }catch(Exception e){\r
-                    logger.error("An exception occurred when trying to flush data");\r
-                    e.printStackTrace();\r
-                    throw new RuntimeException(e);\r
-                }\r
-            }\r
-        }\r
-\r
-        if ( logger.isInfoEnabled() ){logger.info("Saved " + i + " objects" );}\r
-        return resultMap;\r
-    }\r
-\r
-    private UUID saveOrUpdateCdmObject(T cdmObj){\r
-        getSession().saveOrUpdate(cdmObj);\r
-        return cdmObj.getUuid();\r
-    }\r
-\r
-    @Override\r
-    public Map<UUID, T> saveOrUpdateAll(Collection<T> cdmObjCollection){\r
-        int types = cdmObjCollection.getClass().getTypeParameters().length;\r
-        if (types > 0){\r
-            if (logger.isDebugEnabled()){logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);}\r
-        }\r
-\r
-        Map<UUID, T> resultMap = new HashMap<>();\r
-        Iterator<T> iterator = cdmObjCollection.iterator();\r
-        int i = 0;\r
-        while(iterator.hasNext()){\r
-            if ( ( (i % 2000) == 0) && (i > 0)   ){logger.debug("Saved " + i + " objects" );}\r
-            T cdmObj = iterator.next();\r
-            UUID uuid = saveOrUpdateCdmObject(cdmObj);\r
-            if (logger.isDebugEnabled()){logger.debug("Save cdmObj: " + (cdmObj == null? null: cdmObj.toString()));}\r
-            resultMap.put(uuid, cdmObj);\r
-            i++;\r
-            if ( (i % flushAfterNo) == 0){\r
-                try{\r
-                    if (logger.isDebugEnabled()){logger.debug("flush");}\r
-                    flush();\r
-                }catch(Exception e){\r
-                    logger.error("An exception occurred when trying to flush data");\r
-                    e.printStackTrace();\r
-                    throw new RuntimeException(e);\r
-                }\r
-            }\r
-        }\r
-\r
-        if ( logger.isInfoEnabled() ){logger.info("Saved " + i + " objects" );}\r
-        return resultMap;\r
-    }\r
-\r
-\r
-\r
-\r
-    @Override\r
-    public T replace(T x, T y) {\r
-        if(x.equals(y)) {\r
-            return y;\r
-        }\r
-\r
-        Class<?> commonClass = x.getClass();\r
-        if(y != null) {\r
-            while(!commonClass.isAssignableFrom(y.getClass())) {\r
-                if(commonClass.equals(type)) {\r
-                    throw new RuntimeException();\r
-                }\r
-                commonClass = commonClass.getSuperclass();\r
-            }\r
-        }\r
-\r
-        getSession().merge(x);\r
-\r
-        Set<ReferringObjectMetadata> referringObjectMetas = referringObjectMetadataFactory.get(x.getClass());\r
-\r
-        for(ReferringObjectMetadata referringObjectMetadata : referringObjectMetas) {\r
-\r
-          List<CdmBase> referringObjects = referringObjectMetadata.getReferringObjects(x, getSession());\r
-\r
-          for(CdmBase referringObject : referringObjects) {\r
-            try {\r
-                referringObjectMetadata.replace(referringObject,x,y);\r
-                getSession().update(referringObject);\r
-\r
-            } catch (IllegalArgumentException e) {\r
-                throw new RuntimeException(e.getMessage(),e);\r
-            } catch (IllegalAccessException e) {\r
-                throw new RuntimeException(e.getMessage(),e);\r
-            }\r
-          }\r
-        }\r
-        return y;\r
-    }\r
-\r
-    @Override\r
-    public Session getSession() throws DataAccessException {\r
-        return super.getSession();\r
-    }\r
-\r
-    @Override\r
-    public void clear() throws DataAccessException {\r
-        Session session = getSession();\r
-        session.clear();\r
-        if (logger.isDebugEnabled()){logger.debug("dao clear end");}\r
-    }\r
-\r
-    @Override\r
-    public MergeResult<T> merge(T transientObject, boolean returnTransientEntity) throws DataAccessException {\r
-        Session session = getSession();\r
-        PostMergeEntityListener.addSession(session);\r
-        MergeResult<T> result = null;\r
-        try {\r
-            @SuppressWarnings("unchecked")\r
-            T persistentObject = (T)session.merge(transientObject);\r
-            if (logger.isDebugEnabled()){logger.debug("dao merge end");}\r
-\r
-            if(returnTransientEntity) {\r
-                if(transientObject != null && persistentObject != null) {\r
-                    transientObject.setId(persistentObject.getId());\r
-                }\r
-                result = new MergeResult(transientObject, PostMergeEntityListener.getNewEntities(session));\r
-            } else {\r
-                result = new MergeResult(persistentObject, null);\r
-            }\r
-            return result;\r
-        } finally {\r
-            PostMergeEntityListener.removeSession(session);\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public T merge(T transientObject) throws DataAccessException {\r
-        Session session = getSession();\r
-        @SuppressWarnings("unchecked")\r
-               T persistentObject = (T)session.merge(transientObject);\r
-        if (logger.isDebugEnabled()){logger.debug("dao merge end");}\r
-        return persistentObject;\r
-    }\r
-\r
-    @Override\r
-    public UUID saveOrUpdate(T transientObject) throws DataAccessException  {\r
-        if (transientObject == null){\r
-               logger.warn("Object to save should not be null. NOP");\r
-               return null;\r
-        }\r
-       try {\r
-            if (logger.isDebugEnabled()){logger.debug("dao saveOrUpdate start...");}\r
-            if (logger.isDebugEnabled()){logger.debug("transientObject(" + transientObject.getClass().getSimpleName() + ") ID:" + transientObject.getId() + ", UUID: " + transientObject.getUuid()) ;}\r
-            Session session = getSession();\r
-            if(transientObject.getId() != 0 && VersionableEntity.class.isAssignableFrom(transientObject.getClass())) {\r
-                VersionableEntity versionableEntity = (VersionableEntity)transientObject;\r
-                versionableEntity.setUpdated(new DateTime());\r
-                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();\r
-                if(authentication != null && authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {\r
-                  User user = (User)authentication.getPrincipal();\r
-                  versionableEntity.setUpdatedBy(user);\r
-                }\r
-            }\r
-            session.saveOrUpdate(transientObject);\r
-            if (logger.isDebugEnabled()){logger.debug("dao saveOrUpdate end");}\r
-            return transientObject.getUuid();\r
-        } catch (NonUniqueObjectException e) {\r
-            logger.error("Error in CdmEntityDaoBase.saveOrUpdate(obj). ID="+e.getIdentifier()+". Class="+e.getEntityName());\r
-            logger.error(e.getMessage());\r
-\r
-            e.printStackTrace();\r
-            throw e;\r
-        } catch (HibernateException e) {\r
-\r
-            e.printStackTrace();\r
-            throw e;\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public T save(T newInstance) throws DataAccessException {\r
-        if (newInstance == null){\r
-               logger.warn("Object to save should not be null. NOP");\r
-               return null;\r
-        }\r
-       getSession().save(newInstance);\r
-        return newInstance;\r
-    }\r
-\r
-    @Override\r
-    public UUID update(T transientObject) throws DataAccessException {\r
-        if (transientObject == null){\r
-               logger.warn("Object to update should not be null. NOP");\r
-               return null;\r
-        }\r
-       getSession().update(transientObject);\r
-        return transientObject.getUuid();\r
-    }\r
-\r
-    @Override\r
-    public UUID refresh(T persistentObject) throws DataAccessException {\r
-        getSession().refresh(persistentObject);\r
-        return persistentObject.getUuid();\r
-    }\r
-\r
-    @Override\r
-    public UUID delete(T persistentObject) throws DataAccessException {\r
-        if (persistentObject == null){\r
-            logger.warn(type.getName() + " was 'null'");\r
-            return null;\r
-        }\r
-\r
-        // Merge the object in if it is detached\r
-        //\r
-        // I think this is preferable to catching lazy initialization errors\r
-        // as that solution only swallows and hides the exception, but doesn't\r
-        // actually solve it.\r
-       persistentObject = (T) getSession().merge(persistentObject);\r
-        getSession().delete(persistentObject);\r
-        return persistentObject.getUuid();\r
-    }\r
-\r
-       @Override\r
-    public T findById(int id) throws DataAccessException {\r
-        return getSession().get(type, id);\r
-    }\r
-\r
-       @Override\r
-    public T findByUuid(UUID uuid) throws DataAccessException{\r
-           return this.findByUuid(uuid, INCLUDE_UNPUBLISHED);\r
-       }\r
-\r
-    protected T findByUuid(UUID uuid, boolean includeUnpublished) throws DataAccessException{\r
-        Session session = getSession();\r
-        Criteria crit = session.createCriteria(type);\r
-        crit.add(Restrictions.eq("uuid", uuid));\r
-        crit.addOrder(Order.desc("created"));\r
-        if (IPublishable.class.isAssignableFrom(type) && !includeUnpublished ){\r
-            crit.add(Restrictions.eq("publish", Boolean.TRUE));\r
-        }\r
-\r
-        @SuppressWarnings("unchecked")\r
-               List<T> results = crit.list();\r
-        Set<T> resultSet = new HashSet<>();\r
-        resultSet.addAll(results);\r
-        if (resultSet.isEmpty()){\r
-            return null;\r
-        }else{\r
-            if(resultSet.size() > 1){\r
-                logger.error("findByUuid() delivers more than one result for UUID: " + uuid);\r
-            }\r
-            return results.get(0);\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public T findByUuidWithoutFlush(UUID uuid) throws DataAccessException{\r
-       Session session = getSession();\r
-       FlushMode currentFlushMode = session.getFlushMode();\r
-       try {\r
-               // set flush mode to manual so that the session does not flush\r
-               // when before performing the query\r
-               session.setFlushMode(FlushMode.MANUAL);\r
-               Criteria crit = session.createCriteria(type);\r
-               crit.add(Restrictions.eq("uuid", uuid));\r
-               crit.addOrder(Order.desc("created"));\r
-               @SuppressWarnings("unchecked")\r
-                       List<T> results = crit.list();\r
-               if (results.isEmpty()){\r
-                       return null;\r
-               }else{\r
-                       if(results.size() > 1){\r
-                               logger.error("findByUuid() delivers more than one result for UUID: " + uuid);\r
-                       }\r
-                       return results.get(0);\r
-               }\r
-       } finally {\r
-               // set back the session flush mode\r
-               if(currentFlushMode != null) {\r
-                       session.setFlushMode(currentFlushMode);\r
-               }\r
-       }\r
-    }\r
-\r
-    @Override\r
-    public List<T> loadList(Collection<Integer> ids,  List<String> propertyPaths) throws DataAccessException {\r
-\r
-        if (ids.isEmpty()) {\r
-            return new ArrayList<T>(0);\r
-        }\r
-\r
-        Criteria criteria = prepareList(ids, null, null, null, "id");\r
-\r
-        if (logger.isDebugEnabled()){logger.debug(criteria.toString());}\r
-\r
-         @SuppressWarnings("unchecked")\r
-               List<T> result = criteria.list();\r
-         defaultBeanInitializer.initializeAll(result, propertyPaths);\r
-         return result;\r
-     }\r
-\r
-\r
-    @Override\r
-    public List<T> list(Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws DataAccessException {\r
-\r
-        if (uuids == null || uuids.isEmpty()){\r
-            return new ArrayList<>();\r
-        }\r
-\r
-        Criteria criteria = prepareList(uuids, pageSize, pageNumber, orderHints, "uuid");\r
-        @SuppressWarnings("unchecked")\r
-               List<T> result = criteria.list();\r
-        defaultBeanInitializer.initializeAll(result, propertyPaths);\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * {@inheritDoc}\r
-     */\r
-    @Override\r
-    public  List<T> list(Class<? extends T> type, List<Restriction<?>> restrictions,\r
-            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-\r
-        Criteria criteria = criterionForType(type);\r
-\r
-        addRestrictions(restrictions, criteria);\r
-\r
-        addLimitAndStart(limit, start, criteria);\r
-        addOrder(criteria, orderHints);\r
-\r
-        @SuppressWarnings("unchecked")\r
-        List<T> result = criteria.list();\r
-        defaultBeanInitializer.initializeAll(result, propertyPaths);\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * @param restrictions\r
-     * @param criteria\r
-     */\r
-    private void addRestrictions(List<Restriction<?>> restrictions, Criteria criteria) {\r
-\r
-        List<Criterion> perProperty = new ArrayList<>(restrictions.size());\r
-        Map<String, String> aliases = new HashMap<>();\r
-\r
-        for(Restriction<?> propMatchMode : restrictions){\r
-            Collection<? extends Object> values = propMatchMode.getValues();\r
-            if(values != null && !values.isEmpty()){\r
-                Criterion[] predicates = new Criterion[values.size()];\r
-                int i = 0;\r
-                for(Object v : values){\r
-                    String propertyPath = propMatchMode.getPropertyName();\r
-                    // create aliases if the propertyName is a dot separated property path\r
-                    String[] props =  propertyPath.split("\\.");\r
-                    String aliasKey = "";\r
-                    String alias = null;\r
-                    for(int p = 0; p < props.length -1; p++){\r
-                        if(!aliases.containsKey(aliasKey + "_" + props[p])){\r
-                            alias = props[p] + aliases.size();\r
-                            aliases.put(aliasKey, alias);\r
-                            criteria.createAlias(props[p], alias);\r
-                        }\r
-                    }\r
-\r
-                    String propertyName = alias != null ? alias + "." + props[props.length -1] : propertyPath;\r
-                    predicates[i++] = createRestriction(propertyName, v, propMatchMode.getMatchMode());\r
-                }\r
-                perProperty.add(Restrictions.or(predicates));\r
-            }\r
-        }\r
-\r
-        if(!perProperty.isEmpty()){\r
-            criteria.add(Restrictions.and(perProperty.toArray(new Criterion[perProperty.size()])));\r
-        }\r
-    }\r
-\r
-    /**\r
-     * @param propertyName\r
-     * @param value\r
-     * @param matchMode\r
-     * @param criteria\r
-     * @return\r
-     */\r
-    private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {\r
-\r
-        Criterion restriction;\r
-        if(matchMode == null) {\r
-            restriction = Restrictions.eq(propertyName, value);\r
-        } else if(value == null) {\r
-            restriction = Restrictions.isNull(propertyName);\r
-        } else if(!(value instanceof String)) {\r
-            restriction = Restrictions.eq(propertyName, value);\r
-        } else {\r
-            String queryString = (String)value;\r
-            if(matchMode == MatchMode.BEGINNING) {\r
-                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);\r
-            } else if(matchMode == MatchMode.END) {\r
-                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);\r
-            } else if(matchMode == MatchMode.EXACT) {\r
-                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);\r
-            } else {\r
-                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);\r
-            }\r
-        }\r
-        return restriction;\r
-    }\r
-\r
-    /**\r
-     * {@inheritDoc}\r
-     */\r
-    @Override\r
-    public int count(Class<? extends T> type, List<Restriction<?>> restrictions) {\r
-\r
-        Criteria criteria = criterionForType(type);\r
-\r
-        addRestrictions(restrictions, criteria);\r
-\r
-        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));\r
-\r
-        //since hibernate 4 (or so) uniqueResult returns Long, not Integer, therefore needs\r
-        //to be casted. Think about returning long rather then int!\r
-        return ((Number) criteria.uniqueResult()).intValue();\r
-\r
-    }\r
-\r
-    /**\r
-     * @param uuids\r
-     * @param pageSize\r
-     * @param pageNumber\r
-     * @param orderHints\r
-     * @param propertyName\r
-     * @return\r
-     */\r
-    private Criteria prepareList(Collection<?> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, String propertyName) {\r
-        Criteria criteria = getSession().createCriteria(type);\r
-        criteria.add(Restrictions.in(propertyName, uuids));\r
-\r
-        if(pageSize != null) {\r
-            criteria.setMaxResults(pageSize);\r
-            if(pageNumber != null) {\r
-                criteria.setFirstResult(pageNumber * pageSize);\r
-            } else {\r
-                criteria.setFirstResult(0);\r
-            }\r
-        }\r
-\r
-        if(orderHints == null) {\r
-            orderHints = OrderHint.defaultOrderHintsFor(type);\r
-        }\r
-        addOrder(criteria, orderHints);\r
-        return criteria;\r
-    }\r
-\r
-    /**\r
-     *\r
-     * NOTE: We can't reuse {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)\r
-     * here due to different default behavior of the <code>matchmode</code> parameter.\r
-     *\r
-     * @param clazz\r
-     * @param param\r
-     * @param queryString\r
-     * @param matchmode\r
-     * @param criterion\r
-     * @param pageSize\r
-     * @param pageNumber\r
-     * @param orderHints\r
-     * @param propertyPaths\r
-     * @return\r
-     */\r
-    protected List<T> findByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode, List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-\r
-        Criteria criteria = criterionForType(clazz);\r
-\r
-        if (queryString != null) {\r
-            if(matchmode == null) {\r
-                criteria.add(Restrictions.ilike(param, queryString));\r
-            } else if(matchmode == MatchMode.BEGINNING) {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));\r
-            } else if(matchmode == MatchMode.END) {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));\r
-            } else if(matchmode == MatchMode.EXACT) {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));\r
-            } else {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));\r
-            }\r
-        }\r
-\r
-        addCriteria(criteria, criterion);\r
-\r
-        if(pageSize != null) {\r
-            criteria.setMaxResults(pageSize);\r
-            if(pageNumber != null) {\r
-                criteria.setFirstResult(pageNumber * pageSize);\r
-            } else {\r
-                criteria.setFirstResult(0);\r
-            }\r
-        }\r
-\r
-        addOrder(criteria, orderHints);\r
-\r
-        @SuppressWarnings("unchecked")\r
-               List<T> result = criteria.list();\r
-        defaultBeanInitializer.initializeAll(result, propertyPaths);\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * @param clazz\r
-     * @return\r
-     */\r
-    private Criteria criterionForType(Class<? extends T> clazz) {\r
-        Criteria criteria;\r
-        if(clazz == null) {\r
-            criteria = getSession().createCriteria(type);\r
-        } else {\r
-            criteria = getSession().createCriteria(clazz);\r
-        }\r
-        return criteria;\r
-    }\r
-\r
-    @Override\r
-    public T load(UUID uuid) {\r
-        T bean = findByUuid(uuid);\r
-        if(bean == null){\r
-            return null;\r
-        }\r
-        defaultBeanInitializer.load(bean);\r
-\r
-        return bean;\r
-    }\r
-\r
-    @Override\r
-    public T load(int id, List<String> propertyPaths){\r
-        T bean = findById(id);\r
-        if(bean == null){\r
-            return bean;\r
-        }\r
-        defaultBeanInitializer.initialize(bean, propertyPaths);\r
-\r
-        return bean;\r
-    }\r
-\r
-    @Override\r
-    public T load(UUID uuid, List<String> propertyPaths){\r
-        return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);\r
-    }\r
-\r
-    protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths){\r
-        T bean = findByUuid(uuid, includeUnpublished);\r
-        if(bean == null){\r
-            return bean;\r
-        }\r
-        defaultBeanInitializer.initialize(bean, propertyPaths);\r
-\r
-        return bean;\r
-    }\r
-\r
-    @Override\r
-    public Boolean exists(UUID uuid) {\r
-        if (findByUuid(uuid)==null){\r
-            return false;\r
-        }\r
-        return true;\r
-    }\r
-\r
-    @Override\r
-    public int count() {\r
-        return count(type);\r
-    }\r
-\r
-    @Override\r
-    public int count(Class<? extends T> clazz) {\r
-        Session session = getSession();\r
-        Criteria criteria = null;\r
-        if(clazz == null) {\r
-            criteria = session.createCriteria(type);\r
-        } else {\r
-            criteria = session.createCriteria(clazz);\r
-        }\r
-        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));\r
-\r
-        //since hibernate 4 (or so) uniqueResult returns Long, not Integer, therefore needs\r
-        //to be casted. Think about returning long rather then int!\r
-        return ((Number) criteria.uniqueResult()).intValue();\r
-    }\r
-\r
-    @Override\r
-    public List<T> list(Integer limit, Integer start) {\r
-        return list(limit, start, null);\r
-    }\r
-\r
-    @Override\r
-    public List<Object[]> group(Class<? extends T> clazz,Integer limit, Integer start, List<Grouping> groups, List<String> propertyPaths) {\r
-\r
-        Criteria criteria = null;\r
-        criteria = criterionForType(clazz);\r
-\r
-        addGroups(criteria,groups);\r
-\r
-        if(limit != null) {\r
-            criteria.setFirstResult(start);\r
-            criteria.setMaxResults(limit);\r
-        }\r
-\r
-        @SuppressWarnings("unchecked")\r
-               List<Object[]> result = criteria.list();\r
-\r
-        if(propertyPaths != null && !propertyPaths.isEmpty()) {\r
-          for(Object[] objects : result) {\r
-            defaultBeanInitializer.initialize(objects[0], propertyPaths);\r
-          }\r
-        }\r
-\r
-        return result;\r
-    }\r
-\r
-    protected void countGroups(DetachedCriteria criteria,List<Grouping> groups) {\r
-        if(groups != null){\r
-\r
-\r
-            Map<String,String> aliases = new HashMap<String,String>();\r
-\r
-            for(Grouping grouping : groups) {\r
-                if(grouping.getAssociatedObj() != null) {\r
-                    String alias = null;\r
-                    if((alias = aliases.get(grouping.getAssociatedObj())) == null) {\r
-                        alias = grouping.getAssociatedObjectAlias();\r
-                        aliases.put(grouping.getAssociatedObj(), alias);\r
-                        criteria.createAlias(grouping.getAssociatedObj(),alias);\r
-                    }\r
-                }\r
-            }\r
-\r
-            ProjectionList projectionList = Projections.projectionList();\r
-\r
-            for(Grouping grouping : groups) {\r
-                grouping.addProjection(projectionList);\r
-            }\r
-            criteria.setProjection(projectionList);\r
-        }\r
-    }\r
-\r
-    protected void addGroups(Criteria criteria,List<Grouping> groups) {\r
-        if(groups != null){\r
-\r
-\r
-            Map<String,String> aliases = new HashMap<String,String>();\r
-\r
-            for(Grouping grouping : groups) {\r
-                if(grouping.getAssociatedObj() != null) {\r
-                    String alias = null;\r
-                    if((alias = aliases.get(grouping.getAssociatedObj())) == null) {\r
-                        alias = grouping.getAssociatedObjectAlias();\r
-                        aliases.put(grouping.getAssociatedObj(), alias);\r
-                        criteria.createAlias(grouping.getAssociatedObj(),alias);\r
-                    }\r
-                }\r
-            }\r
-\r
-            ProjectionList projectionList = Projections.projectionList();\r
-\r
-            for(Grouping grouping : groups) {\r
-                grouping.addProjection(projectionList);\r
-            }\r
-            criteria.setProjection(projectionList);\r
-\r
-            for(Grouping grouping : groups) {\r
-                grouping.addOrder(criteria);\r
-\r
-            }\r
-        }\r
-    }\r
-\r
-\r
-\r
-\r
-    @Override\r
-    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {\r
-        return list(limit,start,orderHints,null);\r
-    }\r
-\r
-    @Override\r
-    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-        Criteria criteria = getSession().createCriteria(type);\r
-        if(limit != null) {\r
-            criteria.setFirstResult(start);\r
-            criteria.setMaxResults(limit);\r
-        }\r
-\r
-        addOrder(criteria,orderHints);\r
-        @SuppressWarnings("unchecked")\r
-               List<T> results = criteria.list();\r
-\r
-        defaultBeanInitializer.initializeAll(results, propertyPaths);\r
-        return results;\r
-    }\r
-\r
-\r
-    @Override\r
-    public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-        Criteria criteria = null;\r
-        if(clazz == null) {\r
-            criteria = getSession().createCriteria(type);\r
-        } else {\r
-            criteria = getSession().createCriteria(clazz);\r
-        }\r
-\r
-        addLimitAndStart(limit, start, criteria);\r
-\r
-        addOrder(criteria, orderHints);\r
-\r
-        @SuppressWarnings("unchecked")\r
-               List<S> results = criteria.list();\r
-\r
-        defaultBeanInitializer.initializeAll(results, propertyPaths);\r
-        return results;\r
-    }\r
-\r
-\r
-    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {\r
-        return list(type,limit,start,orderHints,null);\r
-    }\r
-\r
-    @Override\r
-    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {\r
-        return list(type,limit,start,null,null);\r
-    }\r
-\r
-    @Override\r
-    public Class<T> getType() {\r
-        return type;\r
-    }\r
-\r
-    protected void setPagingParameter(Query query, Integer pageSize, Integer pageIndex){\r
-        if(pageSize != null) {\r
-            query.setMaxResults(pageSize);\r
-            if(pageIndex != null) {\r
-                query.setFirstResult(pageIndex * pageSize);\r
-            } else {\r
-                query.setFirstResult(0);\r
-            }\r
-        }\r
-    }\r
-\r
-    protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageIndex){\r
-        if(pageSize != null) {\r
-            query.setMaxResults(pageSize);\r
-            if(pageIndex != null) {\r
-                query.setFirstResult(pageIndex * pageSize);\r
-            } else {\r
-                query.setFirstResult(0);\r
-            }\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public int count(T example, Set<String> includeProperties) {\r
-        Criteria criteria = getSession().createCriteria(example.getClass());\r
-        addExample(criteria,example,includeProperties);\r
-\r
-        criteria.setProjection(Projections.rowCount());\r
-        return ((Number)criteria.uniqueResult()).intValue();\r
-    }\r
-\r
-    protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {\r
-        if(includeProperties != null && !includeProperties.isEmpty()) {\r
-            criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));\r
-            ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());\r
-            for(String property : includeProperties) {\r
-                Type type  = classMetadata.getPropertyType(property);\r
-                if(type.isEntityType()) {\r
-                    try {\r
-                        Field field = ReflectionUtils.findField(example.getClass(), property);\r
-                        field.setAccessible(true);\r
-                        Object value =  field.get(example);\r
-                        if(value != null) {\r
-                            criteria.add(Restrictions.eq(property,value));\r
-                        } else {\r
-                            criteria.add(Restrictions.isNull(property));\r
-                        }\r
-                    } catch (SecurityException se) {\r
-                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property, se);\r
-                    } catch (HibernateException he) {\r
-                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property, he);\r
-                    } catch (IllegalArgumentException iae) {\r
-                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property, iae);\r
-                    } catch (IllegalAccessException ie) {\r
-                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property, ie);\r
-                    }\r
-\r
-                }\r
-            }\r
-        } else {\r
-            criteria.add(Example.create(example));\r
-        }\r
-    }\r
-\r
-\r
-    protected long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode, List<Criterion> criterion) {\r
-        Criteria criteria = null;\r
-\r
-        criteria = criterionForType(clazz);\r
-\r
-        if (queryString != null) {\r
-            if(matchmode == null) {\r
-                criteria.add(Restrictions.ilike(param, queryString));\r
-            } else if(matchmode == MatchMode.BEGINNING) {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));\r
-            } else if(matchmode == MatchMode.END) {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));\r
-            } else if(matchmode == MatchMode.EXACT) {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));\r
-            } else {\r
-                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));\r
-            }\r
-        }\r
-\r
-        addCriteria(criteria, criterion);\r
-\r
-        criteria.setProjection(Projections.rowCount());\r
-\r
-        return ((Number)criteria.uniqueResult()).longValue();\r
-    }\r
-\r
-\r
-    @Override\r
-    public List<T> list(T example, Set<String> includeProperties, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-        Criteria criteria = getSession().createCriteria(example.getClass());\r
-        addExample(criteria,example,includeProperties);\r
-\r
-        addLimitAndStart(limit, start, criteria);\r
-\r
-        addOrder(criteria,orderHints);\r
-\r
-        @SuppressWarnings("unchecked")\r
-               List<T> results = criteria.list();\r
-        defaultBeanInitializer.initializeAll(results, propertyPaths);\r
-        return results;\r
-    }\r
-\r
-    private class PropertySelectorImpl implements PropertySelector {\r
-\r
-        private final Set<String> includeProperties;\r
-        /**\r
-         *\r
-         */\r
-        private static final long serialVersionUID = -3175311800911570546L;\r
-\r
-        public PropertySelectorImpl(Set<String> includeProperties) {\r
-            this.includeProperties = includeProperties;\r
-        }\r
-\r
-        @Override\r
-        public boolean include(Object propertyValue, String propertyName,      Type type) {\r
-            if(includeProperties.contains(propertyName)) {\r
-                return true;\r
-            } else {\r
-                return false;\r
-            }\r
-        }\r
-\r
-    }\r
-}\r
-\r
+/**
+* Copyright (C) 2007 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+
+package eu.etaxonomy.cdm.persistence.dao.hibernate.common;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+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 java.util.UUID;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.Criteria;
+import org.hibernate.FlushMode;
+import org.hibernate.HibernateException;
+import org.hibernate.LockOptions;
+import org.hibernate.NonUniqueObjectException;
+import org.hibernate.Session;
+import org.hibernate.criterion.Criterion;
+import org.hibernate.criterion.DetachedCriteria;
+import org.hibernate.criterion.Disjunction;
+import org.hibernate.criterion.Example;
+import org.hibernate.criterion.Example.PropertySelector;
+import org.hibernate.criterion.LogicalExpression;
+import org.hibernate.criterion.Order;
+import org.hibernate.criterion.ProjectionList;
+import org.hibernate.criterion.Projections;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.criterion.Subqueries;
+import org.hibernate.envers.AuditReader;
+import org.hibernate.envers.AuditReaderFactory;
+import org.hibernate.envers.query.AuditQuery;
+import org.hibernate.metadata.ClassMetadata;
+import org.hibernate.sql.JoinType;
+import org.hibernate.type.Type;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.ReflectionUtils;
+
+import eu.etaxonomy.cdm.model.common.CdmBase;
+import eu.etaxonomy.cdm.model.common.IPublishable;
+import eu.etaxonomy.cdm.model.common.VersionableEntity;
+import eu.etaxonomy.cdm.model.permission.User;
+import eu.etaxonomy.cdm.model.view.AuditEvent;
+import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;
+import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
+import eu.etaxonomy.cdm.persistence.dao.common.Restriction.Operator;
+import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
+import eu.etaxonomy.cdm.persistence.dto.MergeResult;
+import eu.etaxonomy.cdm.persistence.hibernate.PostMergeEntityListener;
+import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadata;
+import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadataFactory;
+import eu.etaxonomy.cdm.persistence.query.Grouping;
+import eu.etaxonomy.cdm.persistence.query.MatchMode;
+import eu.etaxonomy.cdm.persistence.query.OrderHint;
+
+/**
+ * Hibernate implementation for {@link ICdmEntityDao}.
+ */
+public abstract class CdmEntityDaoBase<T extends CdmBase>
+        extends DaoBase
+        implements ICdmEntityDao<T> {
+
+    private static final Logger logger = LogManager.getLogger(CdmEntityDaoBase.class);
+
+    protected int flushAfterNo = 1000; // large numbers may cause synchronisation errors
+                                        // when commiting the session
+    protected Class<T> type;
+
+    @Autowired
+    // @Qualifier("defaultBeanInitializer")
+    protected IBeanInitializer defaultBeanInitializer;
+
+    public void setDefaultBeanInitializer(IBeanInitializer defaultBeanInitializer) {
+        this.defaultBeanInitializer = defaultBeanInitializer;
+    }
+
+    @Autowired
+    private ReferringObjectMetadataFactory referringObjectMetadataFactory;
+
+    protected static final EnumSet<Operator> LEFTOUTER_OPS = EnumSet.of(Operator.AND_NOT, Operator.OR, Operator.OR_NOT);
+
+    public CdmEntityDaoBase(Class<T> type) {
+        this.type = type;
+        assert type != null;
+        logger.debug("Creating DAO of type [" + type.getSimpleName() + "]");
+    }
+
+    @Override
+    public void lock(T t, LockOptions lockOptions) {
+        getSession().buildLockRequest(lockOptions).lock(t);
+    }
+
+    @Override
+    public void refresh(T t, LockOptions lockOptions, List<String> propertyPaths) {
+        getSession().refresh(t, lockOptions);
+        defaultBeanInitializer.initialize(t, propertyPaths);
+    }
+
+    // TODO this method should be moved to a concrete class (not typed)
+    public UUID saveCdmObj(CdmBase cdmObj) throws DataAccessException {
+        getSession().saveOrUpdate(cdmObj);
+        return cdmObj.getUuid();
+    }
+
+    // TODO: Replace saveCdmObj() by saveCdmObject_
+    private UUID saveCdmObject_(T cdmObj) {
+        getSession().saveOrUpdate(cdmObj);
+        return cdmObj.getUuid();
+    }
+
+    // TODO: Use everywhere CdmEntityDaoBase.saveAll() instead of
+    // ServiceBase.saveCdmObjectAll()?
+    // TODO: why does this use saveCdmObject_ which actually savesOrUpdateds
+    // data ?
+    @Override
+    public Map<UUID, T> saveAll(Collection<? extends T> cdmObjCollection) {
+        int types = cdmObjCollection.getClass().getTypeParameters().length;
+        if (types > 0) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
+            }
+        }
+
+        Map<UUID, T> resultMap = new HashMap<>();
+        Iterator<? extends T> iterator = cdmObjCollection.iterator();
+        int i = 0;
+        while (iterator.hasNext()) {
+            if (((i % 2000) == 0) && (i > 0)) {
+                logger.debug("Saved " + i + " objects");
+            }
+            T cdmObj = iterator.next();
+            UUID uuid = saveCdmObject_(cdmObj);
+            if (logger.isDebugEnabled()) {
+                logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
+            }
+            resultMap.put(uuid, cdmObj);
+            i++;
+            if ((i % flushAfterNo) == 0) {
+                try {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("flush");
+                    }
+                    flush();
+                } catch (Exception e) {
+                    logger.error("An exception occurred when trying to flush data");
+                    e.printStackTrace();
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        if (logger.isInfoEnabled()) {
+            logger.info("Saved " + i + " objects");
+        }
+        return resultMap;
+    }
+
+    private UUID saveOrUpdateCdmObject(T cdmObj) {
+        getSession().saveOrUpdate(cdmObj);
+        return cdmObj.getUuid();
+    }
+
+    @Override
+    public Map<UUID, T> saveOrUpdateAll(Collection<T> cdmObjCollection) {
+        int types = cdmObjCollection.getClass().getTypeParameters().length;
+        if (types > 0) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
+            }
+        }
+
+        Map<UUID, T> resultMap = new HashMap<>();
+        Iterator<T> iterator = cdmObjCollection.iterator();
+        int i = 0;
+        while (iterator.hasNext()) {
+            if (((i % 2000) == 0) && (i > 0)) {
+                logger.debug("Saved " + i + " objects");
+            }
+            T cdmObj = iterator.next();
+            UUID uuid = saveOrUpdateCdmObject(cdmObj);
+            if (logger.isDebugEnabled()) {
+                logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
+            }
+            resultMap.put(uuid, cdmObj);
+            i++;
+            if ((i % flushAfterNo) == 0) {
+                try {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("flush");
+                    }
+                    flush();
+                } catch (Exception e) {
+                    logger.error("An exception occurred when trying to flush data");
+                    e.printStackTrace();
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        if (logger.isInfoEnabled()) {
+            logger.info("Saved " + i + " objects");
+        }
+        return resultMap;
+    }
+
+    @Override
+    public T replace(T x, T y) {
+        if (x.equals(y)) {
+            return y;
+        }
+
+        Class<?> commonClass = x.getClass();
+        if (y != null) {
+            while (!commonClass.isAssignableFrom(y.getClass())) {
+                if (commonClass.equals(type)) {
+                    throw new RuntimeException();
+                }
+                commonClass = commonClass.getSuperclass();
+            }
+        }
+
+        getSession().merge(x);
+
+        Set<ReferringObjectMetadata> referringObjectMetas = referringObjectMetadataFactory.get(x.getClass());
+
+        for (ReferringObjectMetadata referringObjectMetadata : referringObjectMetas) {
+
+            List<CdmBase> referringObjects = referringObjectMetadata.getReferringObjects(x, getSession());
+
+            for (CdmBase referringObject : referringObjects) {
+                try {
+                    referringObjectMetadata.replace(referringObject, x, y);
+                    getSession().update(referringObject);
+
+                } catch (IllegalArgumentException e) {
+                    throw new RuntimeException(e.getMessage(), e);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+        }
+        return y;
+    }
+
+    @Override
+    public Session getSession() throws DataAccessException {
+        return super.getSession();
+    }
+
+    @Override
+    public void clear() throws DataAccessException {
+        Session session = getSession();
+        session.clear();
+        if (logger.isDebugEnabled()) {
+            logger.debug("dao clear end");
+        }
+    }
+
+    @Override
+    public MergeResult<T> merge(T transientObject, boolean returnTransientEntity) throws DataAccessException {
+        Session session = getSession();
+        PostMergeEntityListener.addSession(session);
+        MergeResult<T> result = null;
+        try {
+            @SuppressWarnings("unchecked")
+            T persistentObject = (T) session.merge(transientObject);
+            if (logger.isDebugEnabled()) {
+                logger.debug("dao merge end");
+            }
+
+            if (returnTransientEntity) {
+                if (transientObject != null && persistentObject != null) {
+                    transientObject.setId(persistentObject.getId());
+                }
+                result = new MergeResult(transientObject, PostMergeEntityListener.getNewEntities(session));
+            } else {
+                result = new MergeResult(persistentObject, null);
+            }
+            return result;
+        } finally {
+            PostMergeEntityListener.removeSession(session);
+        }
+    }
+
+    @Override
+    public T merge(T transientObject) throws DataAccessException {
+        Session session = getSession();
+        @SuppressWarnings("unchecked")
+        T persistentObject = (T) session.merge(transientObject);
+        if (logger.isDebugEnabled()) {
+            logger.debug("dao merge end");
+        }
+        return persistentObject;
+    }
+
+    @Override
+    public UUID saveOrUpdate(T transientObject) throws DataAccessException {
+        if (transientObject == null) {
+            logger.warn("Object to save should not be null. NOP");
+            return null;
+        }
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debug("dao saveOrUpdate start...");
+            }
+            if (logger.isDebugEnabled()) {
+                logger.debug("transientObject(" + transientObject.getClass().getSimpleName() + ") ID:"
+                        + transientObject.getId() + ", UUID: " + transientObject.getUuid());
+            }
+            Session session = getSession();
+            if (transientObject.getId() != 0 && VersionableEntity.class.isAssignableFrom(transientObject.getClass())) {
+                VersionableEntity versionableEntity = (VersionableEntity) transientObject;
+                versionableEntity.setUpdated(new DateTime());
+                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+                if (authentication != null && authentication.getPrincipal() != null
+                        && authentication.getPrincipal() instanceof User) {
+                    User user = (User) authentication.getPrincipal();
+                    versionableEntity.setUpdatedBy(user);
+                }
+            }
+            session.saveOrUpdate(transientObject);
+            if (logger.isDebugEnabled()) {
+                logger.debug("dao saveOrUpdate end");
+            }
+            return transientObject.getUuid();
+        } catch (NonUniqueObjectException e) {
+            logger.error("Error in CdmEntityDaoBase.saveOrUpdate(obj). ID=" + e.getIdentifier() + ". Class="
+                    + e.getEntityName());
+            logger.error(e.getMessage());
+
+            e.printStackTrace();
+            throw e;
+        } catch (HibernateException e) {
+
+            e.printStackTrace();
+            throw e;
+        }
+    }
+
+    @Override
+    public <S extends T> S save(S newInstance) throws DataAccessException {
+        if (newInstance == null) {
+            logger.warn("Object to save should not be null. NOP");
+            return null;
+        }
+        getSession().save(newInstance);
+        return newInstance;
+    }
+
+    @Override
+    public UUID update(T transientObject) throws DataAccessException {
+        if (transientObject == null) {
+            logger.warn("Object to update should not be null. NOP");
+            return null;
+        }
+        getSession().update(transientObject);
+        return transientObject.getUuid();
+    }
+
+    @Override
+    public UUID refresh(T persistentObject) throws DataAccessException {
+        getSession().refresh(persistentObject);
+        return persistentObject.getUuid();
+    }
+
+    @Override
+    public UUID delete(T persistentObject) throws DataAccessException {
+        if (persistentObject == null) {
+            logger.warn(type.getName() + " was 'null'");
+            return null;
+        }
+
+        // Ben Clark:
+        // Merge the object in if it is detached
+        //
+        // I think this is preferable to catching lazy initialization errors
+        // as that solution only swallows and hides the exception, but doesn't
+        // actually solve it.
+        persistentObject = (T) getSession().merge(persistentObject);
+        getSession().delete(persistentObject);
+        return persistentObject.getUuid();
+    }
+
+    @Override
+    public T findById(int id) throws DataAccessException {
+        return getSession().get(type, id);
+    }
+
+    @Override
+    public T findByUuid(UUID uuid) throws DataAccessException {
+        return this.findByUuid(uuid, INCLUDE_UNPUBLISHED);
+    }
+
+    protected T findByUuid(UUID uuid, boolean includeUnpublished) throws DataAccessException {
+        Session session = getSession();
+        Criteria crit = session.createCriteria(type);
+        crit.add(Restrictions.eq("uuid", uuid));
+        crit.addOrder(Order.desc("created"));
+        if (IPublishable.class.isAssignableFrom(type) && !includeUnpublished) {
+            crit.add(Restrictions.eq("publish", Boolean.TRUE));
+        }
+
+        @SuppressWarnings("unchecked")
+        List<T> results = crit.list();
+        Set<T> resultSet = new HashSet<>();
+        resultSet.addAll(results);
+        if (resultSet.isEmpty()) {
+            return null;
+        } else {
+            if (resultSet.size() > 1) {
+                logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
+            }
+            return results.get(0);
+        }
+    }
+
+    @Override
+    public T findByUuidWithoutFlush(UUID uuid) throws DataAccessException {
+        Session session = getSession();
+        FlushMode currentFlushMode = session.getHibernateFlushMode();
+        try {
+            // set flush mode to manual so that the session does not flush
+            // when before performing the query
+            session.setHibernateFlushMode(FlushMode.MANUAL);
+            Criteria crit = session.createCriteria(type);
+            crit.add(Restrictions.eq("uuid", uuid));
+            crit.addOrder(Order.desc("created"));
+            @SuppressWarnings("unchecked")
+            List<T> results = crit.list();
+            if (results.isEmpty()) {
+                return null;
+            } else {
+                if (results.size() > 1) {
+                    logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
+                }
+                return results.get(0);
+            }
+        } finally {
+            // set back the session flush mode
+            if (currentFlushMode != null) {
+                session.setHibernateFlushMode(currentFlushMode);
+            }
+        }
+    }
+
+    @Override
+    public List<T> loadList(Collection<Integer> ids, List<OrderHint> orderHints, List<String> propertyPaths) throws DataAccessException {
+
+        if (ids.isEmpty()) {
+            return new ArrayList<>(0);
+        }
+
+        Criteria criteria = prepareList(null, ids, null, null, orderHints, "id");
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(criteria.toString());
+        }
+
+        @SuppressWarnings("unchecked")
+        List<T> result = criteria.list();
+        defaultBeanInitializer.initializeAll(result, propertyPaths);
+        return result;
+    }
+
+    @Override
+    public List<T> list(Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
+            List<String> propertyPaths) throws DataAccessException {
+
+        if (uuids == null || uuids.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Criteria criteria = prepareList(null, uuids, pageSize, pageNumber, orderHints, "uuid");
+        @SuppressWarnings("unchecked")
+        List<T> result = criteria.list();
+        defaultBeanInitializer.initializeAll(result, propertyPaths);
+        return result;
+    }
+
+    @Override
+    public <S extends T> List<S> list(Class<S> clazz, Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
+            List<String> propertyPaths) throws DataAccessException {
+
+        if (uuids == null || uuids.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Criteria criteria = prepareList(clazz, uuids, pageSize, pageNumber, orderHints, "uuid");
+        @SuppressWarnings("unchecked")
+        List<S> result = criteria.list();
+        defaultBeanInitializer.initializeAll(result, propertyPaths);
+        return result;
+    }
+
+    @Override
+    public <S extends T> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit, Integer start,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+
+        Criteria criteria = createCriteria(type, restrictions, false);
+
+        addLimitAndStart(criteria, limit, start);
+        addOrder(criteria, orderHints);
+
+        @SuppressWarnings("unchecked")
+        List<S> result = criteria.list();
+        defaultBeanInitializer.initializeAll(result, propertyPaths);
+        return result;
+    }
+
+    /**
+     * @param restrictions
+     * @param criteria
+     */
+    private void addRestrictions(List<Restriction<?>> restrictions, DetachedCriteria criteria) {
+
+        if(restrictions == null || restrictions.isEmpty()){
+            return ;
+        }
+
+        List<CriterionWithOperator> perProperty = new ArrayList<>(restrictions.size());
+        Map<String, String> aliases = new HashMap<>();
+
+
+
+        for(Restriction<?> restriction : restrictions){
+            Collection<? extends Object> values = restriction.getValues();
+            JoinType jointype = LEFTOUTER_OPS.contains(restriction.getOperator()) ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN;
+            if(values != null && !values.isEmpty()){
+                // ---
+                String propertyPath = restriction.getPropertyName();
+                String[] props =  propertyPath.split("\\.");
+                String propertyName;
+                if(props.length == 1){
+                    // direct property of the base type of the criteria
+                    propertyName = propertyPath;
+                } else {
+                    // create aliases if the propertyName is a dot separated property path
+                    String aĺiasKey = jointype.name() + "_";
+                    String aliasedProperty = null;
+                    String alias = "";
+                    for(int p = 0; p < props.length -1; p++){
+                        aĺiasKey = aĺiasKey + (aĺiasKey.isEmpty() ? "" : ".") + props[p];
+                        aliasedProperty = alias + (alias.isEmpty() ? "" : ".") + props[p];
+                        if(!aliases.containsKey(aliasedProperty)){
+                            alias = alias + (alias.isEmpty() ? "" : "_" ) + props[p];
+                            aliases.put(aĺiasKey, alias);
+                            criteria.createAlias(aliasedProperty, alias, jointype);
+                            if(logger.isDebugEnabled()){
+                                logger.debug("addRestrictions() alias created with aliasKey " + aĺiasKey + " => " + aliasedProperty + " as " + alias);
+                            }
+                        }
+                    }
+                    propertyName = alias + "." + props[props.length -1];
+                }
+                // ---
+                Criterion[] predicates = new Criterion[values.size()];
+                int i = 0;
+                for(Object v : values){
+                    Criterion criterion = createRestriction(propertyName, v, restriction.getMatchMode());
+                    if(restriction.isNot()){
+                        if(props.length > 1){
+                            criterion = Restrictions.or(Restrictions.not(criterion), Restrictions.isNull(propertyName));
+                        } else {
+                            criterion = Restrictions.not(criterion);
+                        }
+                    }
+                    predicates[i++] = criterion;
+                    if(logger.isDebugEnabled()){
+                        logger.debug("addRestrictions() predicate with " + propertyName + " " + (restriction.getMatchMode() == null ? "=" : restriction.getMatchMode().name()) + " " + v.toString());
+                    }
+                }
+                if(restriction.getOperator() == Operator.AND_NOT){
+                    perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.and(predicates)));
+                } else {
+                    perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.or(predicates)));
+                }
+            } // check has values
+        } // loop over restrictions
+
+        Restriction.Operator firstOperator = null;
+        if(!perProperty.isEmpty()){
+            LogicalExpression logicalExpression = null;
+            for(CriterionWithOperator cwo : perProperty){
+                if(logicalExpression == null){
+                    firstOperator = cwo.operator;
+                    logicalExpression = Restrictions.and(Restrictions.sqlRestriction("1=1"), cwo.criterion);
+                } else {
+                    switch(cwo.operator){
+                        case AND:
+                        case AND_NOT:
+                            logicalExpression = Restrictions.and(logicalExpression, cwo.criterion);
+                            break;
+                        case OR:
+                        case OR_NOT:
+                            logicalExpression = Restrictions.or(logicalExpression, cwo.criterion);
+                            break;
+                        default:
+                            throw new RuntimeException("Unsupported Operator");
+                    }
+                }
+
+            }
+
+
+            criteria.add(logicalExpression);
+//            if(firstOperator == Operator.OR){
+//                // OR
+//            } else {
+//                // AND
+//                criteria.add(Restrictions.and(queryStringCriterion, logicalExpression));
+//            }
+        }
+        if(logger.isDebugEnabled()){
+            logger.debug("addRestrictions() final criteria: " + criteria.toString());
+        }
+    }
+
+    /**
+     * @param propertyName
+     * @param value
+     * @param matchMode
+     *            is only applied if the <code>value</code> is a
+     *            <code>String</code> object
+     * @param criteria
+     * @return
+     */
+    private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {
+
+        Criterion restriction;
+        if (value == null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("createRestriction() " + propertyName + " is null ");
+            }
+            restriction = Restrictions.isNull(propertyName);
+        } else if (matchMode == null || !(value instanceof String)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("createRestriction() " + propertyName + " = " + value.toString());
+            }
+            restriction = Restrictions.eq(propertyName, value);
+        } else {
+            String queryString = (String) value;
+            if (logger.isDebugEnabled()) {
+                logger.debug("createRestriction() " + propertyName + " " + matchMode.getMatchOperator() + " "
+                        + matchMode.queryStringFrom(queryString));
+            }
+            switch(matchMode){
+            case BEGINNING:
+                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);
+                break;
+            case END:
+                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);
+                break;
+            case LIKE:
+                restriction = Restrictions.ilike(propertyName, matchMode.queryStringFrom(queryString), org.hibernate.criterion.MatchMode.ANYWHERE);
+                break;
+            case EXACT:
+                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);
+                break;
+            case ANYWHERE:
+                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
+                break;
+            default:
+                throw new RuntimeException("Unknown MatchMode: " + matchMode.name());
+            }
+        }
+        return restriction;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long count(Class<? extends T> type, List<Restriction<?>> restrictions) {
+
+        Criteria criteria = createCriteria(type, restrictions, false);
+
+        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
+
+        return (Long) criteria.uniqueResult();
+
+    }
+
+    /**
+     * @param uuids
+     * @param pageSize
+     * @param pageNumber
+     * @param orderHints
+     * @param propertyName
+     * @return
+     */
+    private Criteria prepareList(Class<? extends T> clazz, Collection<?> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
+            String propertyName) {
+        if (clazz == null){
+            clazz = type;
+        }
+        Criteria criteria = getSession().createCriteria(clazz);
+        criteria.add(Restrictions.in(propertyName, uuids));
+
+        if (pageSize != null) {
+            criteria.setMaxResults(pageSize);
+            if (pageNumber != null) {
+                criteria.setFirstResult(pageNumber * pageSize);
+            } else {
+                criteria.setFirstResult(0);
+            }
+        }
+
+        if (orderHints == null) {
+            orderHints = OrderHint.defaultOrderHintsFor(type);
+        }
+        addOrder(criteria, orderHints);
+        return criteria;
+    }
+
+    private Criteria criterionForType(Class<? extends T> clazz) {
+        return  getSession().createCriteria(entityType(clazz));
+    }
+
+    protected Class<? extends T> entityType(Class<? extends T> clazz){
+        if (clazz != null) {
+            return clazz;
+        } else {
+            return type;
+        }
+    }
+
+    @Override
+    public T load(UUID uuid) {
+        T bean = findByUuid(uuid);
+        if (bean == null) {
+            return null;
+        }
+        defaultBeanInitializer.load(bean);
+
+        return bean;
+    }
+
+    @Override
+    public T load(int id, List<String> propertyPaths) {
+        T bean = findById(id);
+        if (bean == null) {
+            return bean;
+        }
+        defaultBeanInitializer.initialize(bean, propertyPaths);
+
+        return bean;
+    }
+
+    @Override
+    public T loadWithoutInitializing(int id){
+        return this.getSession().load(type, id);
+    }
+
+    @Override
+    public T load(UUID uuid, List<String> propertyPaths) {
+        return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
+    }
+
+    protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
+        T bean = findByUuid(uuid, includeUnpublished);
+        if (bean == null) {
+            return bean;
+        }
+        defaultBeanInitializer.initialize(bean, propertyPaths);
+
+        return bean;
+    }
+
+    @Override
+    public Boolean exists(UUID uuid) {
+        if (findByUuid(uuid) == null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public long count() {
+        return count(type);
+    }
+
+    @Override
+    public long count(Class<? extends T> clazz) {
+        Session session = getSession();
+        Criteria criteria = null;
+        if (clazz == null) {
+            criteria = session.createCriteria(type);
+        } else {
+            criteria = session.createCriteria(clazz);
+        }
+        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
+
+        // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
+        // therefore needs
+        // to be casted. Think about returning long rather then int!
+        return (long) criteria.uniqueResult();
+    }
+
+    @Override
+    public List<T> list(Integer limit, Integer start) {
+        return list(limit, start, null);
+    }
+
+    @Override
+    public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
+            List<String> propertyPaths) {
+
+        Criteria criteria = null;
+        criteria = criterionForType(clazz);
+
+        addGroups(criteria, groups);
+
+        if (limit != null) {
+            criteria.setFirstResult(start);
+            criteria.setMaxResults(limit);
+        }
+
+        @SuppressWarnings("unchecked")
+        List<Object[]> result = criteria.list();
+
+        if (propertyPaths != null && !propertyPaths.isEmpty()) {
+            for (Object[] objects : result) {
+                defaultBeanInitializer.initialize(objects[0], propertyPaths);
+            }
+        }
+
+        return result;
+    }
+
+    protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
+        if (groups != null) {
+
+            Map<String, String> aliases = new HashMap<String, String>();
+
+            for (Grouping grouping : groups) {
+                if (grouping.getAssociatedObj() != null) {
+                    String alias = null;
+                    if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
+                        alias = grouping.getAssociatedObjectAlias();
+                        aliases.put(grouping.getAssociatedObj(), alias);
+                        criteria.createAlias(grouping.getAssociatedObj(), alias);
+                    }
+                }
+            }
+
+            ProjectionList projectionList = Projections.projectionList();
+
+            for (Grouping grouping : groups) {
+                grouping.addProjection(projectionList);
+            }
+            criteria.setProjection(projectionList);
+        }
+    }
+
+    protected void addGroups(Criteria criteria, List<Grouping> groups) {
+        if (groups != null) {
+
+            Map<String, String> aliases = new HashMap<String, String>();
+
+            for (Grouping grouping : groups) {
+                if (grouping.getAssociatedObj() != null) {
+                    String alias = null;
+                    if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
+                        alias = grouping.getAssociatedObjectAlias();
+                        aliases.put(grouping.getAssociatedObj(), alias);
+                        criteria.createAlias(grouping.getAssociatedObj(), alias);
+                    }
+                }
+            }
+
+            ProjectionList projectionList = Projections.projectionList();
+
+            for (Grouping grouping : groups) {
+                grouping.addProjection(projectionList);
+            }
+            criteria.setProjection(projectionList);
+
+            for (Grouping grouping : groups) {
+                grouping.addOrder(criteria);
+
+            }
+        }
+    }
+
+    @Override
+    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
+        return list(limit, start, orderHints, null);
+    }
+
+    @Override
+    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
+        Criteria criteria = getSession().createCriteria(type);
+        if (limit != null) {
+            criteria.setFirstResult(start);
+            criteria.setMaxResults(limit);
+        }
+
+        addOrder(criteria, orderHints);
+        @SuppressWarnings("unchecked")
+        List<T> results = criteria.list();
+
+        defaultBeanInitializer.initializeAll(results, propertyPaths);
+        return results;
+    }
+
+    @Override
+    public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
+            List<String> propertyPaths) {
+        Criteria criteria = null;
+        if (clazz == null) {
+            criteria = getSession().createCriteria(type);
+        } else {
+            criteria = getSession().createCriteria(clazz);
+        }
+
+        addLimitAndStart(criteria, limit, start);
+
+        addOrder(criteria, orderHints);
+
+        @SuppressWarnings("unchecked")
+        List<S> results = criteria.list();
+
+        defaultBeanInitializer.initializeAll(results, propertyPaths);
+        return results;
+    }
+
+    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
+        return list(type, limit, start, orderHints, null);
+    }
+
+    @Override
+    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
+        return list(type, limit, start, null, null);
+    }
+
+    @Override
+    public Class<T> getType() {
+        return type;
+    }
+
+    @Override
+    public long count(T example, Set<String> includeProperties) {
+        Criteria criteria = getSession().createCriteria(example.getClass());
+        addExample(criteria, example, includeProperties);
+
+        criteria.setProjection(Projections.rowCount());
+        return (Long) criteria.uniqueResult();
+    }
+
+    protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
+        if (includeProperties != null && !includeProperties.isEmpty()) {
+            criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
+            ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
+            for (String property : includeProperties) {
+                Type type = classMetadata.getPropertyType(property);
+                if (type.isEntityType()) {
+                    try {
+                        Field field = ReflectionUtils.findField(example.getClass(), property);
+                        field.setAccessible(true);
+                        Object value = field.get(example);
+                        if (value != null) {
+                            criteria.add(Restrictions.eq(property, value));
+                        } else {
+                            criteria.add(Restrictions.isNull(property));
+                        }
+                    } catch (SecurityException se) {
+                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
+                                se);
+                    } catch (HibernateException he) {
+                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
+                                he);
+                    } catch (IllegalArgumentException iae) {
+                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
+                                iae);
+                    } catch (IllegalAccessException ie) {
+                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
+                                ie);
+                    }
+                }
+            }
+        } else {
+            criteria.add(Example.create(example));
+        }
+    }
+
+    /**
+     *
+     * NOTE: We can't reuse
+     * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
+     * here due to different default behavior of the <code>matchmode</code>
+     * parameter.
+     *
+     * @param clazz
+     * @param param
+     * @param queryString
+     * @param matchmode
+     * @param criterion
+     * @param pageSize
+     * @param pageNumber
+     * @param orderHints
+     * @param propertyPaths
+     * @return
+     */
+    @Override
+    public <S extends T> List<S> findByParam(Class<S> clazz, String param, String queryString, MatchMode matchmode,
+            List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
+            List<String> propertyPaths) {
+        Set<String> stringSet = new HashSet<>();
+        stringSet.add(param);
+        return this.findByParam(clazz, stringSet, queryString, matchmode,
+                criterion, pageSize, pageNumber, orderHints,
+                propertyPaths);
+    }
+
+    @Override
+    public <S extends T> List<S> findByParam(Class<S> clazz, Set<String> params, String queryString, MatchMode matchmode,
+            List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
+            List<String> propertyPaths) {
+
+        Criteria criteria = criterionForType(clazz);
+
+        if (queryString != null) {
+            Set<Criterion> criterions = new HashSet<>();
+            for (String param: params){
+                Criterion crit;
+                if (matchmode == null) {
+                     crit = Restrictions.ilike(param, queryString);
+                } else if (matchmode == MatchMode.BEGINNING) {
+                     crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START);
+                } else if (matchmode == MatchMode.END) {
+                    crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END);
+                } else if (matchmode == MatchMode.EXACT) {
+                    crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT);
+                } else {
+                    crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
+                }
+                criterions.add(crit);
+            }
+            if (criterions.size()>1){
+                Iterator<Criterion> critIterator = criterions.iterator();
+                Disjunction disjunction = Restrictions.disjunction();
+                while (critIterator.hasNext()){
+                    disjunction.add(critIterator.next());
+                }
+                criteria.add(disjunction);
+            }else{
+                if (!criterions.isEmpty()){
+                    criteria.add(criterions.iterator().next());
+                }
+            }
+        }
+
+        addCriteria(criteria, criterion);
+
+        addPageSizeAndNumber(criteria, pageSize, pageNumber);
+        addOrder(criteria, orderHints);
+
+        @SuppressWarnings("unchecked")
+        List<S> result = criteria.list();
+        defaultBeanInitializer.initializeAll(result, propertyPaths);
+        return result;
+    }
+
+    @Override
+    public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
+            List<Criterion> criterion) {
+
+        Criteria criteria = null;
+
+        criteria = criterionForType(clazz);
+
+        if (queryString != null) {
+            if (matchmode == null) {
+                criteria.add(Restrictions.ilike(param, queryString));
+            } else if (matchmode == MatchMode.BEGINNING) {
+                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
+            } else if (matchmode == MatchMode.END) {
+                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
+            } else if (matchmode == MatchMode.EXACT) {
+                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
+            } else {
+                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
+            }
+        }
+
+        addCriteria(criteria, criterion);
+
+        criteria.setProjection(Projections.rowCount());
+
+        return (Long) criteria.uniqueResult();
+    }
+
+    /**
+     * Creates a criteria query for the CDM <code>type</code> either for counting or listing matching entities.
+     * <p>
+     * The set of matching entities can be restricted by passing a list  of {@link Restriction} objects.
+     * Restrictions can logically combined:
+     <pre>
+       Arrays.asList(
+           new Restriction<String>("titleCache", MatchMode.ANYWHERE, "foo"),
+           new Restriction<String>("institute.name", Operator.OR, MatchMode.BEGINNING, "Bar")
+       );
+     </pre>
+     * The first Restriction in the example above by default has the <code>Operator.AND</code> which will be
+     * ignored since this is the first restriction. The <code>Operator</code> of further restrictions in the
+     * list are used to combine with the previous restriction.
+     *
+     * @param type
+     * @param restrictions
+     * @param doCount
+     * @return
+     */
+    protected Criteria createCriteria(Class<? extends T> type, List<Restriction<?>> restrictions, boolean doCount) {
+
+        DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
+        idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
+
+        addRestrictions(restrictions, idsOnlyCriteria);
+
+        Criteria criteria = criterionForType(type);
+        criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
+
+        if(doCount){
+            criteria.setProjection(Projections.rowCount());
+        } else {
+            idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
+        }
+
+        return criteria;
+    }
+
+
+    @Override
+    public <S extends T> List<S> findByParamWithRestrictions(Class<S> clazz, String param, String queryString,
+            MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+
+        List<Restriction<?>> allRestrictions = new ArrayList<>();
+        allRestrictions.add(new Restriction<>(param, matchmode, queryString));
+        if(restrictions != null){
+            allRestrictions.addAll(restrictions);
+        }
+        Criteria criteria = createCriteria(clazz, allRestrictions, false);
+
+        addPageSizeAndNumber(criteria, pageSize, pageNumber);
+
+        addOrder(criteria, orderHints);
+
+        @SuppressWarnings("unchecked")
+        List<S> result = criteria.list();
+        defaultBeanInitializer.initializeAll(result, propertyPaths);
+        return result;
+    }
+
+    @Override
+    public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
+            MatchMode matchmode, List<Restriction<?>> restrictions) {
+
+        List<Restriction<?>> allRestrictions = new ArrayList<>();
+        allRestrictions.add(new Restriction<>(param, matchmode, queryString));
+        if(restrictions != null){
+            allRestrictions.addAll(restrictions);
+        }
+        Criteria criteria = createCriteria(clazz, allRestrictions, true);
+
+        return (Long) criteria.uniqueResult();
+    }
+
+    @Override
+    public <S extends T> List<S> list(S example, Set<String> includeProperties, Integer limit, Integer start,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+        Criteria criteria = getSession().createCriteria(example.getClass());
+        addExample(criteria, example, includeProperties);
+
+        addLimitAndStart(criteria, limit, start);
+
+        addOrder(criteria, orderHints);
+
+        @SuppressWarnings("unchecked")
+        List<S> results = criteria.list();
+        defaultBeanInitializer.initializeAll(results, propertyPaths);
+        return results;
+    }
+
+    private class PropertySelectorImpl implements PropertySelector {
+
+        private final Set<String> includeProperties;
+        /**
+         *
+         */
+        private static final long serialVersionUID = -3175311800911570546L;
+
+        public PropertySelectorImpl(Set<String> includeProperties) {
+            this.includeProperties = includeProperties;
+        }
+
+        @Override
+        public boolean include(Object propertyValue, String propertyName, Type type) {
+            if (includeProperties.contains(propertyName)) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+    }
+
+    private class CriterionWithOperator {
+
+        Restriction.Operator operator;
+        Criterion criterion;
+
+
+        public CriterionWithOperator(Operator operator, Criterion criterion) {
+            super();
+            this.operator = operator;
+            this.criterion = criterion;
+        }
+
+
+    }
+
+    /**
+     * Returns a Criteria for the given {@link Class class} or, if
+     * <code>null</code>, for the base {@link Class class} of this DAO.
+     *
+     * @param clazz
+     * @return the Criteria
+     */
+    protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
+        Criteria criteria = null;
+        if (clazz == null) {
+            criteria = getSession().createCriteria(type);
+        } else {
+            criteria = getSession().createCriteria(clazz);
+        }
+        return criteria;
+    }
+
+    /**
+     * @param clazz
+     * @param auditEvent
+     * @return
+     */
+    protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
+        AuditQuery query = null;
+
+        if (clazz == null) {
+            query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
+        } else {
+            query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
+        }
+        return query;
+    }
+
+    protected AuditReader getAuditReader() {
+        return AuditReaderFactory.get(getSession());
+    }
+}