org.openvpms.component.business.dao.hibernate.im.IMObjectDAOHibernate.java Source code

Java tutorial

Introduction

Here is the source code for org.openvpms.component.business.dao.hibernate.im.IMObjectDAOHibernate.java

Source

/*
 * Version: 1.0
 *
 * The contents of this file are subject to the OpenVPMS License Version
 * 1.0 (the 'License'); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.openvpms.org/license/
 *
 * Software distributed under the License is distributed on an 'AS IS' basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Copyright 2014 (C) OpenVPMS Ltd. All Rights Reserved.
 */

package org.openvpms.component.business.dao.hibernate.im;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.openvpms.component.business.dao.hibernate.im.common.CompoundAssembler;
import org.openvpms.component.business.dao.hibernate.im.common.Context;
import org.openvpms.component.business.dao.hibernate.im.common.ContextHandler;
import org.openvpms.component.business.dao.hibernate.im.common.DOState;
import org.openvpms.component.business.dao.hibernate.im.common.DeferredAssembler;
import org.openvpms.component.business.dao.hibernate.im.common.DeferredReference;
import org.openvpms.component.business.dao.hibernate.im.common.DeleteHandler;
import org.openvpms.component.business.dao.hibernate.im.common.IMObjectDO;
import org.openvpms.component.business.dao.hibernate.im.common.IMObjectDOImpl;
import org.openvpms.component.business.dao.hibernate.im.entity.DefaultObjectLoader;
import org.openvpms.component.business.dao.hibernate.im.entity.HibernateResultCollector;
import org.openvpms.component.business.dao.hibernate.im.entity.IMObjectNodeResultCollector;
import org.openvpms.component.business.dao.hibernate.im.entity.IMObjectResultCollector;
import org.openvpms.component.business.dao.hibernate.im.entity.NodeSetResultCollector;
import org.openvpms.component.business.dao.hibernate.im.entity.ObjectSetResultCollector;
import org.openvpms.component.business.dao.hibernate.im.lookup.LookupReplacer;
import org.openvpms.component.business.dao.hibernate.im.query.QueryBuilder;
import org.openvpms.component.business.dao.hibernate.im.query.QueryContext;
import org.openvpms.component.business.dao.im.Page;
import org.openvpms.component.business.dao.im.common.IMObjectDAO;
import org.openvpms.component.business.dao.im.common.IMObjectDAOException;
import org.openvpms.component.business.dao.im.common.ResultCollector;
import org.openvpms.component.business.domain.im.archetype.descriptor.ArchetypeDescriptor;
import org.openvpms.component.business.domain.im.common.IMObject;
import org.openvpms.component.business.domain.im.common.IMObjectReference;
import org.openvpms.component.business.domain.im.lookup.Lookup;
import org.openvpms.component.business.service.archetype.descriptor.cache.IArchetypeDescriptorCache;
import org.openvpms.component.system.common.query.ArchetypeQuery;
import org.openvpms.component.system.common.query.IArchetypeQuery;
import org.openvpms.component.system.common.query.IPage;
import org.openvpms.component.system.common.query.NamedQuery;
import org.openvpms.component.system.common.query.NodeSet;
import org.openvpms.component.system.common.query.ObjectSet;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.ClassNameMustBeSpecified;
import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.FailedToDeleteIMObject;
import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.FailedToExecuteNamedQuery;
import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.FailedToExecuteQuery;
import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.FailedToFindIMObjects;
import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.FailedToSaveCollectionOfObjects;
import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.FailedToSaveIMObject;
import static org.openvpms.component.business.dao.im.common.IMObjectDAOException.ErrorCode.InvalidQueryString;

/**
 * This is an implementation of the IMObject DAO for hibernate.
 *
 * @author Jim Alateras
 * @author Tim Anderson
 */
public class IMObjectDAOHibernate extends HibernateDaoSupport implements IMObjectDAO, ContextHandler {

    /**
     * Transaction helper.
     */
    private TransactionTemplate txnTemplate;

    /**
     * The handler factory.
     */
    private DeleteHandlerFactory handlerFactory;

    /**
     * The assembler.
     */
    private CompoundAssembler assembler;

    /**
     * The archetype descriptor cache.
     * This is used to resolve {@link IMObjectReference}s.
     */
    private IArchetypeDescriptorCache cache;

    /**
     * The logger.
     */
    private static final Log log = LogFactory.getLog(IMObjectDAOHibernate.class);

    /**
     * Constructs an {@link IMObjectDAOHibernate}.
     *
     * @param factory the session factory
     */
    public IMObjectDAOHibernate(SessionFactory factory) {
        setSessionFactory(factory);
    }

    /**
     * Sets the transaction manager.
     *
     * @param manager the transaction manager
     */
    public void setTransactionManager(PlatformTransactionManager manager) {
        txnTemplate = new TransactionTemplate(manager);
    }

    /**
     * Sets the archetype descriptor cache.
     *
     * @param cache the archetype descriptor cache
     */
    public void setArchetypeDescriptorCache(IArchetypeDescriptorCache cache) {
        this.cache = cache;
        assembler = new AssemblerImpl(cache);
        handlerFactory = new DeleteHandlerFactory(assembler, cache);
    }

    /**
     * Saves an object.
     *
     * @param object the object to save
     * @throws IMObjectDAOException if the request cannot complete
     */
    public void save(final IMObject object) {
        try {
            update(new HibernateCallback<Object>() {
                public Object doInHibernate(Session session) throws HibernateException {
                    save(object, session);
                    return null;
                }
            });
        } catch (Throwable exception) {
            throw new IMObjectDAOException(FailedToSaveIMObject, exception, object.getId());
        }
    }

    /**
     * Saves a collection of objects in a single transaction.
     *
     * @param objects the objects to save
     * @throws IMObjectDAOException if the request cannot complete
     */
    public void save(final Collection<? extends IMObject> objects) {
        try {
            update(new HibernateCallback<Object>() {
                public Object doInHibernate(Session session) throws HibernateException {
                    save(objects, session);
                    return null;
                }
            });
        } catch (Throwable exception) {
            throw new IMObjectDAOException(FailedToSaveCollectionOfObjects, exception);
        }
    }

    /**
     * Deletes an {@link IMObject}.
     *
     * @param object the object to delete
     * @throws IMObjectDAOException if the request cannot complete
     */
    public void delete(final IMObject object) {
        try {
            update(new HibernateCallback<Object>() {
                public Object doInHibernate(Session session) throws HibernateException {
                    Context context = getContext(session);
                    DeleteHandler handler = handlerFactory.getHandler(object);
                    handler.delete(object, session, context);
                    updateIds(context);
                    return null;
                }
            });
        } catch (IMObjectDAOException exception) {
            throw exception;
        } catch (Throwable exception) {
            throw new IMObjectDAOException(FailedToDeleteIMObject, exception, object.getObjectReference());
        }
    }

    /**
     * Retrieves the objects matching the query.
     *
     * @param query the archetype query
     * @return a page of objects that match the query criteria
     * @throws IMObjectDAOException for any error
     */
    public IPage<IMObject> get(IArchetypeQuery query) {
        return getQueryDelegator(query).get(query);
    }

    /**
     * Retrieves partially populated objects that match the query.
     * This may be used to selectively load parts of object graphs to improve
     * performance.
     * <p/>
     * All simple properties of the returned objects are populated - the
     * <code>nodes</code> argument is used to specify which collection nodes to
     * populate. If empty, no collections will be loaded, and the behaviour of
     * accessing them is undefined.
     *
     * @param query the archetype query
     * @param nodes the collection node names
     * @return a page of objects that match the query criteria
     * @throws IMObjectDAOException for any error
     */
    public IPage<IMObject> get(IArchetypeQuery query, Collection<String> nodes) {
        return getQueryDelegator(query).get(query, nodes);
    }

    /**
     * Retrieves the objects matching the query.
     *
     * @param query the archetype query
     * @return a page of objects that match the query criteria
     * @throws IMObjectDAOException for any error
     */
    public IPage<ObjectSet> getObjects(IArchetypeQuery query) {
        return getQueryDelegator(query).getObjects(query);
    }

    /**
     * Retrieves the nodes from the objects that match the query criteria.
     *
     * @param query the archetype query
     * @param nodes the node names
     * @return the nodes for each object that matches the query criteria
     * @throws IMObjectDAOException for any error
     */
    public IPage<NodeSet> getNodes(IArchetypeQuery query, Collection<String> nodes) {
        return getQueryDelegator(query).getNodes(query, nodes);
    }

    /**
     * Execute a get using the specified query string, the query
     * parameters and the result collector. The first row and the number of rows
     * is used to control the paging of the result set.
     *
     * @param queryString the query string
     * @param parameters  the query parameters
     * @param firstResult the first result to retrieve
     * @param maxResults  the maximum number of results to return
     * @param count       if <code>true</code> counts the total no. of results,
     *                    returning it in {@link IPage#getTotalResults()}
     * @throws IMObjectDAOException for any error
     */
    public void get(String queryString, Map<String, Object> parameters, ResultCollector collector, int firstResult,
            int maxResults, boolean count) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("query=" + queryString + ", parameters=" + parameters);
            }
            executeQuery(queryString, new Params(parameters), (HibernateResultCollector) collector, firstResult,
                    maxResults, count);
        } catch (Exception exception) {
            throw new IMObjectDAOException(FailedToExecuteQuery, exception, queryString);
        }
    }

    /**
     * Retrieve the objects that matches the specified search criteria.
     * This is a very generic method that provides a mechanism to return
     * objects based on, one or more criteria.
     * <p/>
     * All parameters are optional and can either denote an exact or partial
     * match semantics. If a parameter has a '*' at the start or end of the
     * value then it will perform a wildcard match.  If not '*' is specified in
     * the value then it will only return objects with the exact value.
     * <p/>
     * If two or more parameters are specified then it will return entities
     * that matching all criteria.
     * <p/>
     * The results will be returned in a {@link Page} object, which may contain
     * a subset of the total result set. The caller can then use the context
     * information in the {@link Page} object to make subsequent calls.
     *
     * @param rmName       the reference model name
     * @param entityName   the entity name
     * @param conceptName  the concept name
     * @param instanceName the instance name
     * @param clazz        the fully qualified name of the class to search for
     * @param activeOnly   indicates whether to return active objects.
     * @param firstResult  the first result to retrieve
     * @param maxResults   the maximum number of results to return
     * @return IPage<IMObject>
     *         the results and associated context information
     * @throws IMObjectDAOException a runtime exception if the request cannot
     *                              complete
     * @deprecated replaced by {@link #get(String, String, String, boolean,
     *             int, int)}
     */
    @Deprecated
    public IPage<IMObject> get(String rmName, String entityName, String conceptName, String instanceName,
            String clazz, boolean activeOnly, int firstResult, int maxResults) {
        StringBuilder shortName = new StringBuilder();
        if (entityName != null) {
            shortName.append(entityName);
        } else {
            shortName.append("*");
        }
        shortName.append(".");
        if (conceptName != null) {
            shortName.append(conceptName);
        } else {
            shortName.append("*");
        }
        return get(shortName.toString(), instanceName, clazz, activeOnly, firstResult, maxResults);
    }

    /**
     * Retrieve the objects that matches the specified search criteria.
     * This is a very generic method that provides a mechanism to return
     * objects based on, one or more criteria.
     * <p/>
     * All parameters are optional and can either denote an exact or partial
     * match semantics. If a parameter has a '*' at the start or end of the
     * value then it will perform a wildcard match.  If not '*' is specified in
     * the value then it will only return objects with the exact value.
     * <p/>
     * If two or more parameters are specified then it will return entities
     * that matching all criteria.
     * <p/>
     * The results will be returned in a {@link Page} object, which may contain
     * a subset of the total result set. The caller can then use the context
     * information in the {@link Page} object to make subsequent calls.
     *
     * @param shortName    the archetype short name
     * @param instanceName the instance name
     * @param clazz        the fully qualified name of the class to search for
     * @param activeOnly   indicates whether to return active objects.
     * @param firstResult  the first result to retrieve
     * @param maxResults   the maximum number of results to return
     * @return a page of the results
     * @throws IMObjectDAOException a runtime exception if the request cannot
     *                              complete
     */
    public IPage<IMObject> get(String shortName, String instanceName, String clazz, boolean activeOnly,
            int firstResult, int maxResults) {
        clazz = assembler.getDOClassName(clazz);
        if (clazz == null) {
            throw new IMObjectDAOException(ClassNameMustBeSpecified);
        }

        StringBuilder queryString = new StringBuilder();
        List<String> names = new ArrayList<String>();
        List<Object> params = new ArrayList<Object>();
        boolean andRequired = false;

        queryString.append("from ");
        queryString.append(clazz);
        queryString.append(" as entity");

        // check to see if one or more of the values have been specified
        if (!StringUtils.isEmpty(shortName) || !StringUtils.isEmpty(instanceName)) {
            queryString.append(" where ");
        }

        // process the shortName
        if (!StringUtils.isEmpty(shortName)) {
            names.add("shortName");
            andRequired = true;
            if (shortName.endsWith("*") || shortName.startsWith("*")) {
                queryString.append(" entity.archetypeId.shortName like :shortName");
                params.add(shortName.replace("*", "%"));
            } else {
                queryString.append(" entity.archetypeId.shortName = :shortName");
                params.add(shortName);
            }
        }

        // process the instance name
        if (!StringUtils.isEmpty(instanceName)) {
            if (andRequired) {
                queryString.append(" and ");
            }

            names.add("instanceName");
            andRequired = true;
            if (instanceName.endsWith("*") || instanceName.startsWith("*")) {
                queryString.append(" entity.name like :instanceName");
                params.add(instanceName.replace("*", "%"));
            } else {
                queryString.append(" entity.name = :instanceName");
                params.add(instanceName);
            }
        }

        // determine if we are only interested in active objects
        if (activeOnly) {
            if (andRequired) {
                queryString.append(" and ");
            }
            queryString.append(" entity.active = 1");
        }

        if (log.isDebugEnabled()) {
            log.debug("Executing " + queryString + " with names " + names.toString() + " and params "
                    + params.toString());
        }

        try {
            HibernateResultCollector<IMObject> collector = new IMObjectResultCollector();
            executeQuery(queryString.toString(), new Params(names, params), collector, firstResult, maxResults,
                    true);
            return collector.getPage();
        } catch (Exception exception) {
            throw new IMObjectDAOException(FailedToFindIMObjects, exception, shortName, instanceName, clazz);
        }
    }

    /**
     * Returns an object with the specified reference.
     *
     * @param reference the object reference
     * @return the corresponding object, or <tt>null</tt> if none exists
     * @throws IMObjectDAOException for any error
     */
    public IMObject get(final IMObjectReference reference) {
        // first determine if the object is cached in the transaction context
        IMObject result = execute(new HibernateCallback<IMObject>() {
            public IMObject doInHibernate(Session session) {
                Context context = getContext(session);
                DOState state = context.getCached(reference);
                return (state != null) ? state.getSource() : null;
            }
        });
        if (result == null && !reference.isNew()) {
            // no cached object. If the reference indicates the object is 
            // committed, try and query it
            ArchetypeDescriptor desc = cache.getArchetypeDescriptor(reference.getArchetypeId());
            if (desc != null) {
                result = get(desc.getClassName(), reference);
            }
        }
        return result;
    }

    /**
     * Execute a get using the specified named query, the query
     * parameters and the result collector. The first result and the number of
     * results is used to control the paging of the result set.
     *
     * @param query       the query name
     * @param parameters  the query parameters
     * @param collector   the result collector
     * @param firstResult the first result to retrieve
     * @param maxResults  the maximum number of results to return
     * @param count       if <code>true</code> counts the total no. of results,
     *                    returning it in {@link IPage#getTotalResults()}
     * @throws IMObjectDAOException for any error
     */
    public void getByNamedQuery(String query, Map<String, Object> parameters, ResultCollector collector,
            int firstResult, int maxResults, boolean count) {
        try {
            executeNamedQuery(query, parameters, firstResult, maxResults, (HibernateResultCollector) collector,
                    count);
        } catch (Exception exception) {
            throw new IMObjectDAOException(FailedToExecuteNamedQuery, exception);
        }
    }

    /**
     * Invoked just prior to commit.
     * </p>
     * Attempts to save any deferred objects.
     *
     * @param context the assembly context
     */
    public void preCommit(Context context) {
        if (!context.getSaveDeferred().isEmpty()) {
            saveDeferred(context, true);
        }
    }

    /**
     * Invoked after successful commit.
     * <p/>
     * This propagates identifier and version changes from the committed
     * <tt>IMObjectDO</tt>s to their corresponding <tt>IMObject</tt>s.
     *
     * @param context the assembly context
     */
    public void commit(Context context) {
        updateIds(context);
    }

    /**
     * Invoked on transaction rollback.
     * <p/>
     * This reverts identifier and version changes
     *
     * @param context the assembly context
     */
    public void rollback(Context context) {
        for (DOState state : context.getSaved()) {
            state.rollbackIds();
        }
    }

    /**
     * Replaces the uses of one lookup with another.
     *
     * @param source the lookup to replace
     * @param target the lookup to replace <tt>source</tt> it with
     */
    public void replace(final Lookup source, final Lookup target) {
        try {
            update(new HibernateCallback<Object>() {
                public Object doInHibernate(Session session) throws HibernateException {
                    LookupReplacer replacer = new LookupReplacer(cache);
                    replacer.replace(source, target, session);
                    return null;
                }
            });
        } catch (Throwable exception) {
            throw new IMObjectDAOException(FailedToSaveIMObject, exception, source.getId());
        }
    }
    /*
    * (non-Javadoc)
    *
    * @see org.springframework.orm.hibernate3.support.HibernateDaoSupport#createHibernateTemplate(org.hibernate.SessionFactory)
    */

    @Override
    protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) {
        HibernateTemplate template = super.createHibernateTemplate(sessionFactory);
        template.setFlushMode(HibernateTemplate.FLUSH_COMMIT);

        // note - need to expose the native session in order for
        // Session.equals() to work in Context.getContext()
        template.setExposeNativeSession(true);

        return template;
    }

    /**
     * This method will execute a query and paginate the result set.
     *
     * @param queryString the hql query
     * @param params      the query parameters
     * @param collector   the collector
     * @param firstResult the first row to return
     * @param maxResults  the number of rows to return
     * @param count       if <tt>true</tt>, count the results matching the criteria
     * @throws Exception for any error
     */
    private void executeQuery(final String queryString, final Params params,
            final HibernateResultCollector collector, final int firstResult, final int maxResults,
            final boolean count) throws Exception {
        execute(new HibernateCallback<Object>() {

            public Object doInHibernate(Session session) throws HibernateException {
                collector.setFirstResult(firstResult);
                collector.setPageSize(maxResults);
                collector.setSession(session);
                Context context = getContext(session);
                collector.setContext(context);

                if (maxResults == 0 && count) {
                    // only want a count of the results matching the criteria
                    int rowCount = count(queryString, params, session);
                    collector.setTotalResults(rowCount);
                } else {
                    Query query = session.createQuery(queryString);
                    params.setParameters(query);

                    // set the first result
                    if (firstResult != 0) {
                        query.setFirstResult(firstResult);
                    }

                    // set the maximum number of rows
                    if (maxResults != ArchetypeQuery.ALL_RESULTS) {
                        query.setMaxResults(maxResults);
                        log.debug("The maximum number of rows is " + maxResults);
                    }

                    query.setCacheable(true);

                    List rows = query.list();
                    if (maxResults == ArchetypeQuery.ALL_RESULTS) {
                        collector.setTotalResults(rows.size());
                    } else if (count) {
                        int rowCount = count(queryString, params, session);
                        if (rowCount < rows.size()) {
                            // rows deleted since initial query
                            rowCount = rows.size();
                        }
                        collector.setTotalResults(rowCount);
                    } else {
                        collector.setTotalResults(-1);
                    }
                    for (Object object : rows) {
                        collector.collect(object);
                    }
                    resolveDeferredReferences(context);
                }
                return null;
            }
        });
    }

    /**
     * This method will execute a query and paginate the result set
     *
     * @param name      the name of query
     * @param params    the name and value of the parameters
     * @param firstRow  the first row to return
     * @param numOfRows the number of rows to return
     * @param collector the collector
     * @param count     if <tt>true</tt> counts the total no. of rows, returning it in {@link IPage#getTotalResults()}
     * @throws Exception for any error
     */
    private void executeNamedQuery(final String name, final Map<String, Object> params, final int firstRow,
            final int numOfRows, final HibernateResultCollector collector, final boolean count) throws Exception {
        execute(new HibernateCallback<Object>() {
            @SuppressWarnings("unchecked")
            public Object doInHibernate(Session session) throws HibernateException {
                // session.setFlushMode(FlushMode.MANUAL);
                Query query = session.getNamedQuery(name);
                Params p = new Params(params);
                p.setParameters(query);

                // set first row
                if (firstRow != 0) {
                    query.setFirstResult(firstRow);
                }

                // set maximum rows
                if (numOfRows != ArchetypeQuery.ALL_RESULTS) {
                    query.setMaxResults(numOfRows);
                    log.debug("The maximum number of rows is " + numOfRows);
                }

                List<Object> rows = query.list();
                collector.setFirstResult(firstRow);
                collector.setPageSize(numOfRows);
                collector.setSession(session);
                Context context = getContext(session);
                collector.setContext(context);

                if (numOfRows == ArchetypeQuery.ALL_RESULTS) {
                    collector.setTotalResults(rows.size());
                } else if (count) {
                    int rowCount = countNamedQuery(name, p, session);
                    if (rowCount < rows.size()) {
                        // rows deleted since initial query
                        rowCount = rows.size();
                    }
                    collector.setTotalResults(rowCount);
                } else {
                    collector.setTotalResults(-1);
                }
                for (Object object : rows) {
                    collector.collect(object);
                }
                resolveDeferredReferences(context);
                return null;
            }
        });
    }

    /**
     * Counts the total no. of rows that would be returned by a query.
     *
     * @param queryString the query string
     * @param params      the query parameters
     * @param session     the hibernate session
     * @return the total no. of rows that would be returned by the query
     * @throws IMObjectDAOException a runtime exception, raised if the request
     *                              cannot complete.
     */
    private int count(String queryString, Params params, Session session) {
        int indexOfFrom = queryString.indexOf("from");
        if (indexOfFrom == -1) {
            throw new IMObjectDAOException(InvalidQueryString, queryString);
        }

        Query query = session.createQuery("select count(*) " + queryString.substring(indexOfFrom));

        params.setParameters(query);
        return ((Number) query.list().get(0)).intValue();
    }

    /**
     * Counts the total no. of rows that would be returned by a named query.
     *
     * @param name    the query name
     * @param params  the query parameters
     * @param session the session
     * @return the total no. of rows that would be returned by the query
     * @throws IMObjectDAOException a runtime exception, raised if the request
     *                              cannot complete.
     */
    private int countNamedQuery(String name, Params params, Session session) {
        Query query = session.getNamedQuery(name);
        params.setParameters(query);

        int result = 0;
        ScrollableResults results = null;
        try {
            results = query.scroll(ScrollMode.FORWARD_ONLY);
            if (results.last()) {
                result = results.getRowNumber() + 1;
            }
        } finally {
            if (results != null) {
                results.close();
            }
        }
        return result;
    }

    /**
     * Helper to map query parameters into a form used by hibernate.
     */
    private static class Params {

        private String[] names;

        private Object[] values;

        public Params(List<String> names, List<Object> values) {
            this.names = names.toArray(new String[names.size()]);
            this.values = values.toArray();
        }

        public Params(Map<String, Object> params) {
            names = params.keySet().toArray(new String[params.keySet().size()]);
            values = new Object[names.length];
            for (int i = 0; i < names.length; ++i) {
                values[i] = params.get(names[i]);
            }
        }

        public void setParameters(Query query) {
            for (int i = 0; i < names.length; ++i) {
                if (values[i] instanceof Collection) {
                    query.setParameterList(names[i], (Collection) values[i]);
                } else if (values[i] instanceof Object[]) {
                    query.setParameterList(names[i], (Object[]) values[i]);
                } else {
                    query.setParameter(names[i], values[i]);
                }
            }
        }

        public String[] getNames() {
            return names;
        }

        public Object[] getValues() {
            return values;
        }
    }

    /**
     * Retrieves an object given its class and reference.
     *
     * @param clazz     the object's class
     * @param reference the object reference
     * @return the corresponding object, or <tt>null</tt> if none is found
     */
    private IMObject get(String clazz, final IMObjectReference reference) {
        clazz = assembler.getDOClassName(clazz);
        if (clazz == null) {
            throw new IMObjectDAOException(ClassNameMustBeSpecified);
        }
        final StringBuffer queryString = new StringBuffer();

        queryString.append("select entity from ");
        queryString.append(clazz);
        queryString.append(" as entity where entity.id = :id and entity.archetypeId.shortName = :shortName");

        return execute(new HibernateCallback<IMObject>() {
            public IMObject doInHibernate(Session session) throws HibernateException {
                Query query = session.createQuery(queryString.toString());
                query.setParameter("id", reference.getId());
                query.setParameter("shortName", reference.getArchetypeId().getShortName());
                List results = query.list();
                if (results.size() == 0) {
                    return null;
                } else {
                    Context context = getContext(session);
                    IMObjectDO object = (IMObjectDO) results.get(0);
                    IMObject result = assembler.assemble(object, context);
                    resolveDeferredReferences(context);
                    return result;
                }
            }
        });
    }

    /**
     * Save an object.
     *
     * @param object  the object to save
     * @param session the session to use
     */
    private void save(IMObject object, Session session) {
        Context context = getContext(session);
        boolean deferred = !context.getSaveDeferred().isEmpty();
        save(object, context);
        if (deferred) {
            saveDeferred(context, false);
        }
        if (!context.isSynchronizationActive()) { // todo - required?
            preCommit(context);
        }
    }

    /**
     * Saves an object.
     *
     * @param object  the object to save
     * @param context the context
     */
    private void save(IMObject object, Context context) {
        DOState state = assembler.assemble(object, context);
        if (state.isComplete()) {
            save(state, context);
        } else {
            context.addSaveDeferred(state);
        }
    }

    /**
     * Save a collection of objects.
     *
     * @param objects the objects to save
     * @param session the session to use
     */
    private void save(Collection<? extends IMObject> objects, Session session) {
        Context context = getContext(session);
        boolean deferred = !context.getSaveDeferred().isEmpty();
        for (IMObject object : objects) {
            save(object, context);
        }
        if (deferred || context.getSaveDeferred().size() > 1) {
            saveDeferred(context, false);
        }
        if (!context.isSynchronizationActive()) {
            preCommit(context);
        }
    }

    /**
     * Propagates identifier and version changes from the committed
     * <tt>IMObjectDO</tt>s to their corresponding <tt>IMObject</tt>s.
     *
     * @param context the assembly context
     */
    private void updateIds(Context context) {
        for (DOState state : context.getSaved()) {
            state.updateIds(context);
        }
    }

    /**
     * Attempts to assemble and save deferred objects.
     *
     * @param context          the assembly context
     * @param failOnIncomplete if <tt>true</tt> and an object cannot be saved,
     *                         raises an exception
     * @throws IMObjectDAOException if an object cannot be saved
     */
    private void saveDeferred(Context context, boolean failOnIncomplete) {
        List<DOState> states = assembleDeferred(context);
        for (DOState state : states) {
            save(state, context);
        }
        Set<DOState> saveDeferred = context.getSaveDeferred();
        if (!saveDeferred.isEmpty() && failOnIncomplete) {
            DOState state = saveDeferred.iterator().next();
            Set<DeferredAssembler> deferred = state.getDeferred();
            IMObjectReference ref = null;
            if (!deferred.isEmpty()) { // should never be empty
                DeferredAssembler assembler = deferred.iterator().next();
                ref = assembler.getReference();
            }
            throw new IMObjectDAOException(IMObjectDAOException.ErrorCode.ObjectNotFound, ref);

        }
    }

    /**
     * Attempts to assemble deferred objects.
     *
     * @param context the assembly context
     * @return the assembled objects
     */
    private List<DOState> assembleDeferred(Context context) {
        List<DOState> result = new ArrayList<DOState>();
        boolean processed;
        do {
            processed = false;
            DOState[] states = context.getSaveDeferred().toArray(new DOState[context.getSaveDeferred().size()]);
            Set<DeferredAssembler> deferred = new HashSet<DeferredAssembler>();
            for (DOState state : states) {
                Set<DeferredAssembler> set = state.getDeferred();
                if (!set.isEmpty()) {
                    deferred.addAll(set);
                } else {
                    context.removeSaveDeferred(state);
                    result.add(state);
                }
            }
            if (!deferred.isEmpty()) {
                for (DeferredAssembler assembler : deferred) {
                    if (context.getCached(assembler.getReference()) != null) {
                        assembler.assemble(context);
                        processed = true;
                    }
                }
            }
        } while (processed);
        return result;
    }

    /**
     * Saves a data object.
     *
     * @param state   the data object state
     * @param context the assembly context
     */
    private void save(DOState state, Context context) {
        Session session = context.getSession();
        for (IMObjectDO object : state.getObjects()) {
            session.saveOrUpdate(object);
        }
        state.updateIds(context);
        context.addSaved(state);
    }

    /**
     * Resolves deferred references.
     *
     * @param context the assembly context
     */
    private void resolveDeferredReferences(Context context) {
        List<DeferredReference> deferred = context.getDeferredReferences();
        if (!deferred.isEmpty()) {
            Map<Class<? extends IMObjectDOImpl>, List<DeferredReference>> map = new HashMap<Class<? extends IMObjectDOImpl>, List<DeferredReference>>();
            for (DeferredReference ref : deferred) {
                IMObjectDO object = ref.getObject();
                if (Hibernate.isInitialized(object)) {
                    ref.update(object.getObjectReference());
                } else {
                    List<DeferredReference> list = map.get(ref.getType());
                    if (list == null) {
                        list = new ArrayList<DeferredReference>();
                        map.put(ref.getType(), list);
                    }
                    list.add(ref);
                }
            }
            if (!map.isEmpty()) {
                for (Map.Entry<Class<? extends IMObjectDOImpl>, List<DeferredReference>> entry : map.entrySet()) {
                    Class<? extends IMObjectDOImpl> type = entry.getKey();
                    List<DeferredReference> refs = entry.getValue();
                    Map<Long, IMObjectDO> objects = new HashMap<Long, IMObjectDO>();
                    for (DeferredReference ref : refs) {
                        IMObjectDO object = ref.getObject();
                        objects.put(object.getId(), object);
                    }
                    Map<Long, IMObjectReference> resolvedRefs = context.getReferences(objects, type);
                    for (DeferredReference ref : refs) {
                        IMObjectReference resolved = resolvedRefs.get(ref.getObject().getId());
                        if (resolved != null) {
                            ref.update(resolved);
                        }
                    }
                }
                map.clear();
            }
            deferred.clear();
        }
    }

    /**
     * Check whether write operations are allowed on the given Session.
     * <p>Default implementation throws an InvalidDataAccessApiUsageException
     * in case of FlushMode.NEVER. Can be overridden in subclasses.
     *
     * @param template the hibernate template
     * @param session  the current session
     * @throws InvalidDataAccessApiUsageException
     *          if write operations are not allowed
     * @see HibernateTemplate#checkWriteOperationAllowed(Session)
     */
    private void checkWriteOperationAllowed(HibernateTemplate template, Session session)
            throws InvalidDataAccessApiUsageException {
        if (template.isCheckWriteOperations() && template.getFlushMode() != HibernateTemplate.FLUSH_EAGER
                && FlushMode.MANUAL.equals(session.getFlushMode())) {
            throw new InvalidDataAccessApiUsageException("Write operations are not allowed in read-only mode "
                    + "(FlushMode.MANUAL) - turn your Session into "
                    + "FlushMode.AUTO or remove 'readOnly' marker from " + "transaction definition");
        }
    }

    /**
     * Executes an update within a transaction.
     *
     * @param callback the callback to execute
     * @return the result of the callback
     */
    private Object update(final HibernateCallback<Object> callback) {
        final HibernateTemplate template = getHibernateTemplate();
        getHibernateTemplate().setExposeNativeSession(true);
        return txnTemplate.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                return template.execute(new HibernateCallback<Object>() {
                    public Object doInHibernate(Session session) throws HibernateException {
                        checkWriteOperationAllowed(template, session);
                        return template.execute(callback);
                    }
                });
            }
        });
    }

    /**
     * Returns the assembly context for the specified session,
     * creating one if it doesn't exist.
     *
     * @param session the session
     * @return the corresponding assembly context
     */
    private Context getContext(Session session) {
        Context context = Context.getContext(assembler, session);
        context.setContextHandler(this);
        return context;
    }

    /**
     * Executes an hibernate callback.
     *
     * @param callback the callback
     * @return the result of the callback
     */
    private <T> T execute(HibernateCallback<T> callback) {
        final HibernateTemplate template = getHibernateTemplate();
        template.setExposeNativeSession(true);
        return template.execute(callback);
    }

    /**
     * Returns a new query delegator for the specified query.
     *
     * @param query the query
     * @return a new query delegator for the query
     */
    private QueryDelegator getQueryDelegator(IArchetypeQuery query) {
        if (query instanceof ArchetypeQuery) {
            return new DefaultQueryDelegator();
        } else if (query instanceof NamedQuery) {
            return new NamedQueryDelegator();
        }
        throw new IllegalArgumentException("Unsupported query: " + query.getClass().getName());
    }

    abstract class QueryDelegator {

        public IPage<IMObject> get(IArchetypeQuery query) {
            HibernateResultCollector<IMObject> collector = new IMObjectResultCollector();
            collector.setLoader(new DefaultObjectLoader());
            get(query, collector);
            return collector.getPage();
        }

        public IPage<IMObject> get(IArchetypeQuery query, Collection<String> nodes) {
            HibernateResultCollector<IMObject> collector = new IMObjectNodeResultCollector(cache, nodes);
            collector.setLoader(new DefaultObjectLoader());
            get(query, collector);
            return collector.getPage();
        }

        public IPage<NodeSet> getNodes(IArchetypeQuery query, Collection<String> nodes) {
            HibernateResultCollector<NodeSet> collector = new NodeSetResultCollector(cache, nodes);
            collector.setLoader(new DefaultObjectLoader());
            get(query, collector);
            return collector.getPage();
        }

        public abstract IPage<ObjectSet> getObjects(IArchetypeQuery query);

        protected abstract void get(IArchetypeQuery query, ResultCollector collector);
    }

    class DefaultQueryDelegator extends QueryDelegator {

        public IPage<ObjectSet> getObjects(IArchetypeQuery query) {
            QueryBuilder builder = new QueryBuilder(cache, assembler);
            QueryContext context = builder.build((ArchetypeQuery) query);
            HibernateResultCollector<ObjectSet> collector = new ObjectSetResultCollector(context.getSelectNames(),
                    context.getRefSelectNames(), context.getSelectTypes());
            collector.setLoader(new DefaultObjectLoader());
            get(context, query, collector);
            return collector.getPage();
        }

        protected void get(IArchetypeQuery query, ResultCollector collector) {
            QueryBuilder builder = new QueryBuilder(cache, assembler);
            QueryContext context = builder.build((ArchetypeQuery) query);
            get(context, query, collector);
        }

        private void get(QueryContext context, IArchetypeQuery query, ResultCollector collector) {
            if (log.isDebugEnabled()) {
                log.debug("ArchetypeService.get: query " + context.getQueryString());
            }

            IMObjectDAOHibernate.this.get(context.getQueryString(), context.getParameters(), collector,
                    query.getFirstResult(), query.getMaxResults(), query.countResults());
        }
    }

    class NamedQueryDelegator extends QueryDelegator {

        public IPage<ObjectSet> getObjects(IArchetypeQuery query) {
            NamedQuery q = (NamedQuery) query;
            List<String> names = (q.getNames() != null) ? new ArrayList<String>(q.getNames()) : null;
            List<String> refNames = Collections.emptyList();
            HibernateResultCollector<ObjectSet> collector = new ObjectSetResultCollector(names, refNames, null);
            collector.setLoader(new DefaultObjectLoader());
            get(query, collector);
            return collector.getPage();
        }

        protected void get(IArchetypeQuery query, ResultCollector collector) {
            NamedQuery q = (NamedQuery) query;
            getByNamedQuery(q.getQuery(), q.getParameters(), collector, q.getFirstResult(), q.getMaxResults(),
                    q.countResults());
        }
    }

}