org.jpos.gl.GLSession.java Source code

Java tutorial

Introduction

Here is the source code for org.jpos.gl.GLSession.java

Source

/*
 * jPOS Project [http://jpos.org]
 * Copyright (C) 2000-2012 Alejandro P. Revilla
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.jpos.gl;

import java.util.*;
import java.math.BigDecimal;

import org.hibernate.*;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Disjunction;
import org.hibernate.type.LongType;

import org.jpos.ee.DB;

/**
 * MiniGL facility entry point.
 *
 * @author <a href="mailto:apr@jpos.org">Alejandro Revilla</a>
 */
public class GLSession {
    private static Map<String, Object> ruleCache = new HashMap<String, Object>();
    private GLUser user;
    private Session session;
    private DB db;
    public static final short[] LAYER_ZERO = new short[] { 0 };
    public static final BigDecimal ZERO = new BigDecimal("0.00");
    public static final BigDecimal Z = new BigDecimal("0");

    /**
     * Construct a GLSession for a given user.
     *
     * User has to exist in MiniGL gluser table unless username is null.
     * @see GLUser
     *
     * @param username the user name.
     * @throws org.hibernate.HibernateException on database problems
     * @throws GLException if user is not valid
     */
    public GLSession(String username) throws HibernateException, GLException {
        super();
        this.db = new DB();
        session = db.open();
        if (username != null) {
            user = getUser(username);
            if (user == null) {
                close();
                throw new GLException("Invalid user '" + username + "'");
            }
        }
    }

    /**
     * Construct a GLSession using property <code>user.name</code>.
     * User has to exist in MiniGL gluser table.
     * @see GLUser
     * @throws org.hibernate.HibernateException on database problems
     * @throws GLException if user.name is not valid
     */
    public GLSession() throws HibernateException, GLException {
        this(System.getProperty("user.name"));
    }

    /**
     * Construct a GLSession for a given user.
     *
     * User has to exist in MiniGL gluser table unless username is null.
     * @see GLUser
     *
     * @param db EE DB
     * @param username the user name.
     * @throws HibernateException on database related issue
     * @throws GLException if user is invalid
     */
    public GLSession(DB db, String username) throws HibernateException, GLException {
        super();
        this.db = db;
        boolean autoClose = false;
        if (db.session() == null) {
            db.open();
            autoClose = !autoClose;
        }
        session = db.session();
        if (username != null) {
            user = getUser(username);
            if (user == null) {
                if (autoClose)
                    close();
                throw new GLException("Invalid user '" + username + "'");
            }
        }
    }

    /**
     * Construct a GLSession using property <code>user.name</code>.
     * User has to exist in MiniGL gluser table.
     * @param db EE DB
     * @see GLUser
     * @throws org.hibernate.HibernateException on hibernate exception
     * @throws GLException on GL level exception
     */
    public GLSession(DB db) throws HibernateException, GLException {
        this(db, System.getProperty("user.name"));
    }

    /**
     * @param action name
     * @return true if user has permission to perform given action
     * @see GLPermission
     */
    public boolean hasPermission(String action) {
        Iterator iter = user.getPermissions().iterator();
        while (iter.hasNext()) {
            GLPermission p = (GLPermission) iter.next();
            if (p.getJournal() == null && action.equals(p.getName()))
                return true;
        }
        return false;
    }

    /**
     * @param action name
     * @throws GLException if user doesn't have permission.
     * @see GLPermission
     */
    public void checkPermission(String action) throws GLException {
        if (!hasPermission(action)) {
            throw new GLException("User '" + user.getName() + "' (" + user.getId() + ") does not have '" + action
                    + "' permission.");
        }
    }

    /**
     * Grant permission to user.
     * In order to grant a permission, we need to have both the permission
     * and GRANT.
     *
     * @param userName user name
     * @param permName permission name
     */
    public void grant(String userName, String permName) throws GLException, HibernateException {
        checkPermission(GLPermission.GRANT);
        checkPermission(permName);
        GLPermission perm = new GLPermission(permName);
        session.save(perm);
        GLUser u = getUser(userName);
        u.grant(perm);
    }

    /**
     * Revoke permission from user.
     * In order to grant a permission, we need to have both the permission
     * and GRANT.
     *
     * @param userName user name
     * @param permName permission name
     */
    public void revoke(String userName, String permName) throws GLException, HibernateException {
        checkPermission(GLPermission.GRANT);
        GLUser u = getUser(userName);
        u.revoke(permName);
    }

    /**
    * Grant permission to user no matter if we have the premission nor GRANT.
    *
    * @param userName user name
    * @param permName permission name
    */
    public void forceGrant(String userName, String permName) {
        GLPermission perm = new GLPermission(permName);
        session.save(perm);
        GLUser u = getUser(userName);
        u.grant(perm);
    }

    /**
     * Revoke permission from user no matter if we have the permission nor GRANT.
     *
     * @param userName user name
     * @param permName permission name
     */
    public void forceRevoke(String userName, String permName) {
        GLUser u = getUser(userName);
        u.revoke(permName);
    }

    /**
     * Verifies user's permission in a given journal.
     * @param action name
     * @param j journal
     * @return true if user has permission to perform given action.
     * @see GLPermission
     * @see Journal
     */
    public boolean hasPermission(String action, Journal j) {
        return getUser().hasPermission(action, j);
    }

    /**
     * Check user's permission in a given journal.
     * @param action name
     * @param j journal
     * @throws GLException if user doesn't have permission.
     * @see GLPermission
     * @see Journal
     */
    public void checkPermission(String action, Journal j) throws GLException {
        if (!hasPermission(action, j)) {
            throw new GLException("User '" + user.getName() + "' (" + user.getId() + ") does not have '" + action
                    + "' permission in journal '" + j.getName() + "' (" + j.getId() + ")");
        }
    }

    /**
     * @param code chart of account's code
     * @return top level chart with given code or null.
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public Account getChart(String code) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session
                .createQuery("from acct in class org.jpos.gl.CompositeAccount where code=:code and parent is null");
        q.setParameter("code", code);
        Iterator iter = q.list().iterator();
        return (Account) (iter.hasNext() ? iter.next() : null);
    }

    /**
     * @return List of charts of accounts.
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public List<Account> getCharts() throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session.createQuery("from acct in class org.jpos.gl.CompositeAccount where parent is null");
        return q.list();
    }

    /**
     * @param chart chart of accounts.
     * @param code  account's code.
     * @return account with given code in given chart, or null.
     *
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public Account getAccount(Account chart, String code) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session.createQuery("from acct in class org.jpos.gl.Account where root=:chart and code=:code");
        q.setLong("chart", chart.getId());
        q.setParameter("code", code);
        Iterator iter = q.list().iterator();
        return (Account) (iter.hasNext() ? iter.next() : null);
    }

    /**
     * Add account to parent.
     * Check permissions, parent's type and optional currency.
     *
     * @param parent parent account
     * @param acct account to add
     * @throws HibernateException on error
     * @throws GLException if user doesn't have permissions, or type mismatch
     */
    public void addAccount(CompositeAccount parent, Account acct) throws HibernateException, GLException {
        addAccount(parent, acct, false);
    }

    /**
     * Add account to parent.
     * Check permissions, parent's type and optional currency.
     *
     * @param parent parent account
     * @param acct account to add
     * @param fast true if we want a fast add that do not eagerly load all childrens
     * @throws HibernateException on error
     * @throws GLException if user doesn't have permissions, or type mismatch
     */
    @SuppressWarnings("unchecked")
    public void addAccount(CompositeAccount parent, Account acct, boolean fast)
            throws HibernateException, GLException {
        checkPermission(GLPermission.WRITE);
        if (!parent.isChart() && !parent.equalsType(acct)) {
            StringBuffer sb = new StringBuffer("Type mismatch ");
            sb.append(parent.getTypeAsString());
            sb.append('/');
            sb.append(acct.getTypeAsString());
            throw new GLException(sb.toString());
        }
        String currencyCode = parent.getCurrencyCode();
        if (currencyCode != null && !currencyCode.equals(acct.getCurrencyCode())) {
            StringBuffer sb = new StringBuffer("Currency mismatch ");
            sb.append(currencyCode);
            sb.append('/');
            sb.append(acct.getCurrencyCode());
            throw new GLException(sb.toString());
        }
        acct.setRoot(parent.getRoot());
        session.save(acct);
        acct.setParent(parent);
        if (!fast)
            parent.getChildren().add(acct);
    }

    /**
     * Add a chart of accounts.
     * Check permissions.
     *
     * @param acct chart to add
     * @throws HibernateException on error
     * @throws GLException if user doesn't have write permission
     */
    public void addChart(Account acct) throws HibernateException, GLException {
        checkPermission(GLPermission.WRITE);
        session.save(acct);
    }

    /**
     * @param chart chart of accounts.
     * @param code  account's code.
     * @return final account with given code in given chart, or null.
     *
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public FinalAccount getFinalAccount(Account chart, String code) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session
                .createQuery("from acct in class org.jpos.gl.FinalAccount where root=:chart and code=:code");
        q.setLong("chart", chart.getId());
        q.setParameter("code", code);
        Iterator iter = q.list().iterator();
        return (FinalAccount) (iter.hasNext() ? iter.next() : null);
    }

    /**
     * @param chart chart of accounts.
     * @return list of final accounts
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public List<FinalAccount> getFinalAccounts(Account chart) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session.createQuery("from acct in class org.jpos.gl.FinalAccount where root=:chart");
        q.setLong("chart", chart.getId());
        return (List<FinalAccount>) q.list();
    }

    /**
     * @param parent parent account.
     * @return list of composite accounts children of the parent account
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public List<CompositeAccount> getCompositeChildren(Account parent) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session.createQuery("from acct in class org.jpos.gl.CompositeAccount where parent=:parent");
        q.setParameter("parent", parent);
        return (List<CompositeAccount>) q.list();
    }

    /**
     * @param parent parent account.
     * @return list of composite accounts children of the parent account
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public List<FinalAccount> getFinalChildren(Account parent) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session.createQuery("from acct in class org.jpos.gl.FinalAccount where parent=:parent");
        q.setParameter("parent", parent);
        return (List<FinalAccount>) q.list();
    }

    /**
     * @param chart chart of accounts.
     * @return list of all accounts
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public List<Account> getAllAccounts(Account chart) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session.createQuery("from acct in class org.jpos.gl.Account where root=:chart");
        q.setLong("chart", chart.getId());
        return (List<Account>) q.list();
    }

    /**
     * @param chart chart of accounts.
     * @param code  account's code.
     * @return composite account with given code in given chart, or null.
     *
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public CompositeAccount getCompositeAccount(Account chart, String code) throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session
                .createQuery("from acct in class org.jpos.gl.CompositeAccount where root=:chart and code=:code");
        q.setLong("chart", chart.getId());
        q.setParameter("code", code);
        Iterator iter = q.list().iterator();
        return (CompositeAccount) (iter.hasNext() ? iter.next() : null);
    }

    /**
     * @param chartName chart of account's code.
     * @param code  account's code.
     * @return account with given code in given chart, or null.
     *
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public Account getAccount(String chartName, String code) throws HibernateException, GLException {
        Account chart = getChart(chartName);
        if (chart == null)
            throw new GLException("Chart '" + chartName + "' does not exist");
        return getAccount(chart, code);
    }

    /**
     * @param chartName chart of account's code.
     * @param code  account's code.
     * @return final account with given code in given chart, or null.
     *
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public FinalAccount getFinalAccount(String chartName, String code) throws HibernateException, GLException {
        Account chart = getChart(chartName);
        if (chart == null)
            throw new GLException("Chart '" + chartName + "' does not exist");
        return getFinalAccount(chart, code);
    }

    /**
     * @param chartName chart of account's code.
     * @param code  account's code.
     * @return composite account with given code in given chart, or null.
     *
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     * @see GLPermission
     */
    public CompositeAccount getCompositeAccount(String chartName, String code)
            throws HibernateException, GLException {
        Account chart = getChart(chartName);
        if (chart == null)
            throw new GLException("Chart '" + chartName + "' does not exist");
        return getCompositeAccount(chart, code);
    }

    /**
     * @param name journal's name.
     * @return journal or null.
     * @throws GLException if users doesn't have global READ permission.
     * @throws HibernateException on database errors.
     * @see GLPermission
     */
    public Journal getJournal(String name) throws HibernateException, GLException {
        Query q = session.createQuery("from journal in class org.jpos.gl.Journal where name=:name");
        q.setParameter("name", name);
        Iterator iter = q.list().iterator();
        Journal j = iter.hasNext() ? (Journal) iter.next() : null;
        if (j == null)
            throw new GLException("Journal '" + name + "' does not exist");
        checkPermission(GLPermission.READ, j);
        return j;
    }

    /**
    * @return list of all journals
    * @throws HibernateException on database errors.
    * @throws GLException if users doesn't have global READ permission.
    * @see GLPermission
    */
    public List<Journal> getAllJournals() throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        Query q = session.createQuery("from acct in class org.jpos.gl.Journal order by chart");
        return (List<Journal>) q.list();
    }

    /**
     * Post transaction in a given journal.
     *
     * @param journal the journal.
     * @param txn the transaction.
     * @throws GLException if user doesn't have POST permission or any rule associated with this journal and/or account raises a GLException.
     * @throws HibernateException on database errors.
     * @see GLPermission
     * @see JournalRule
     */
    public void post(Journal journal, GLTransaction txn) throws HibernateException, GLException {
        checkPermission(GLPermission.POST, journal);
        txn.setJournal(journal);
        txn.setTimestamp(new Date());
        if (txn.getPostDate() == null)
            txn.setPostDate(txn.getTimestamp());
        else
            invalidateCheckpoints(txn);
        Collection rules = getRules(txn);
        // dumpRules (rules);
        applyRules(txn, rules);
        session.save(txn);
    }

    /**
     * Moves a transaction to a new journal
     * @param txn the Transaction
     * @param journal the New Journal
     * @throws GLException if user doesn't have POST permission on the old and new journals.
     * @throws HibernateException on database errors.
     */
    public void move(GLTransaction txn, Journal journal) throws GLException, HibernateException {
        checkPermission(GLPermission.POST, journal);
        checkPermission(GLPermission.POST, txn.getJournal());
        invalidateCheckpoints(txn); // invalidate in old journal
        txn.setJournal(journal);
        invalidateCheckpoints(txn); // invalidate in new journal
        applyRules(txn, getRules(txn));
        session.update(txn);
    }

    /**
     * Summarize transactions in a journal.
     *
     * @param journal the journal.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @param description summary transaction's description
     * @return GLTransaction a summary transaction
     * @throws GLException if user doesn't have READ permission on this journal.
     * @throws HibernateException on database/mapping errors
     */
    public GLTransaction summarize(Journal journal, Date start, Date end, String description, short[] layers)
            throws HibernateException, GLException {
        checkPermission(GLPermission.SUMMARIZE, journal);
        start = Util.floor(start);
        end = Util.ceil(end);

        if (end.compareTo(start) < 0) {
            throw new GLException("Invalid date range " + Util.dateToString(start) + ":" + Util.dateToString(end));
        }
        Date lockDate = journal.getLockDate();
        if (lockDate != null && start.compareTo(lockDate) <= 0) {
            throw new GLException("Journal is locked at " + Util.dateToString(lockDate));
        }
        setLockDate(journal, end);

        GLTransaction txn = new GLTransaction(description);
        for (int i = 0; i < layers.length; i++) {
            Iterator debits = findSummarizedGLEntries(journal, start, end, false, layers[i]);
            Iterator credits = findSummarizedGLEntries(journal, start, end, true, layers[i]);
            while (debits.hasNext()) {
                Object[] obj = (Object[]) debits.next();
                txn.createDebit((FinalAccount) obj[0], (BigDecimal) obj[1], null, layers[i]);
            }
            while (credits.hasNext()) {
                Object[] obj = (Object[]) credits.next();
                txn.createCredit((FinalAccount) obj[0], (BigDecimal) obj[1], null, layers[i]);
            }
        }
        txn.setJournal(journal);
        txn.setTimestamp(new Date());
        txn.setPostDate(end);
        deleteGLTransactions(journal, start, end);
        session.save(txn); // force post - no rule validations
        journal.setLockDate(null);
        return txn;
    }

    /**
     * @param journal the journal.
     * @param id txn id
     * @return GLTransaction or null
     * @throws GLException if user doesn't have READ permission on this journal.
     */
    public GLTransaction getTransaction(Journal journal, long id) throws HibernateException, GLException {
        GLTransaction txn = null;
        checkPermission(GLPermission.READ, journal);
        try {
            txn = (GLTransaction) session.load(GLTransaction.class, new Long(id));
            if (!txn.getJournal().equals(journal))
                throw new GLException("The transaction does not belong to the specified journal");
        } catch (ObjectNotFoundException e) {
            // okay to happen
        }
        return txn;
    }

    /**
     * @param journal the journal.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @param searchString optional search string
     * @param findByPostDate true to find by postDate, false to find by timestamp
     * @param pageNumber the page number
     * @param pageSize the page size
     * @return list of transactions
     * @throws GLException if user doesn't have READ permission on this journal.
     */
    public Criteria createFindTransactionsCriteria(Journal journal, Date start, Date end, String searchString,
            boolean findByPostDate, int pageNumber, int pageSize) throws HibernateException, GLException {
        int firstResult = 0;
        if (pageSize > 0 && pageNumber > 0)
            firstResult = pageSize * (pageNumber - 1);

        return createFindTransactionsCriteriaByRange(journal, start, end, searchString, findByPostDate, firstResult,
                pageSize);
    }

    /**
     * @param journal the journal.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @param searchString optional search string
     * @param findByPostDate true to find by postDate, false to find by timestamp
     * @param firstResult the first result
     * @param pageSize the page size
     * @return list of transactions
     * @throws GLException if user doesn't have READ permission on this journal.
     */
    public Criteria createFindTransactionsCriteriaByRange(Journal journal, Date start, Date end,
            String searchString, boolean findByPostDate, int firstResult, int pageSize)
            throws HibernateException, GLException {
        checkPermission(GLPermission.READ, journal);
        String dateField = findByPostDate ? "postDate" : "timestamp";
        if (findByPostDate) {
            if (start != null)
                start = Util.floor(start);
            if (end != null)
                end = Util.ceil(end);
        }
        Criteria crit = session.createCriteria(GLTransaction.class).add(Restrictions.eq("journal", journal));

        if (start != null && start.equals(end))
            crit.add(Restrictions.eq(dateField, start));
        else {
            if (start != null)
                crit.add(Restrictions.ge(dateField, start));
            if (end != null)
                crit.add(Restrictions.le(dateField, end));
        }
        if (searchString != null)
            crit.add(Restrictions.like("detail", "%" + searchString + "%"));

        if (pageSize > 0 && firstResult > 0) {
            crit.setMaxResults(pageSize);
            crit.setFirstResult(firstResult);
        }
        return crit;
    }

    /**
     * @param journal the journal.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @param searchString optional search string
     * @param findByPostDate true to find by postDate, false to find by timestamp
     * @param pageNumber the page number
     * @param pageSize the page size
     * @return list of transactions
     * @throws GLException if user doesn't have READ permission on this journal.
     */
    public List findTransactions(Journal journal, Date start, Date end, String searchString, boolean findByPostDate,
            int pageNumber, int pageSize) throws HibernateException, GLException {
        return createFindTransactionsCriteria(journal, start, end, searchString, findByPostDate, pageNumber,
                pageSize).list();
    }

    /**
     * @param journal the journal.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @param searchString optional search string
     * @param findByPostDate true to find by postDate, false to find by timestamp
     * @return list of transactions
     * @throws GLException if user doesn't have READ permission on this journal.
     */
    public List findTransactions(Journal journal, Date start, Date end, String searchString, boolean findByPostDate)
            throws HibernateException, GLException {
        return findTransactions(journal, start, end, searchString, findByPostDate, 0, 0);
    }

    /**
     * @param journal the journal.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @param searchString optional search string
     * @param findByPostDate true to find by postDate, false to find by timestamp
     * @return list of transactions' ids
     * @throws GLException if user doesn't have READ permission on this journal.
     */
    public List findTransactionsIds(Journal journal, Date start, Date end, String searchString,
            boolean findByPostDate, int pageNumber, int pageSize) throws HibernateException, GLException {
        checkPermission(GLPermission.READ, journal);
        String dateField = findByPostDate ? "postDate" : "timestamp";
        if (findByPostDate) {
            if (start != null)
                start = Util.floor(start);
            if (end != null)
                end = Util.ceil(end);
        }
        Criteria crit = session.createCriteria(GLTransaction.class).add(Restrictions.eq("journal", journal));
        crit.setProjection(Projections.id());
        if (start != null && start.equals(end))
            crit.add(Restrictions.eq(dateField, start));
        else {
            if (start != null)
                crit.add(Restrictions.ge(dateField, start));
            if (end != null)
                crit.add(Restrictions.le(dateField, end));
        }
        if (searchString != null)
            crit.add(Restrictions.like("detail", "%" + searchString + "%"));
        if (pageSize > 0 && pageNumber > 0) {
            crit.setMaxResults(pageSize);
            crit.setFirstResult(pageSize * (pageNumber - 1));
        }
        return crit.list();
    }

    /**
     * @param journal the journal.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @param searchString optional search string
     * @param findByPostDate true to find by postDate, false to find by timestamp
     * @return number of transactions
     * @throws GLException if user doesn't have READ permission on this journal.
     */
    public Long findTransactionsRowCount(Journal journal, Date start, Date end, String searchString,
            boolean findByPostDate) throws HibernateException, GLException {
        checkPermission(GLPermission.READ, journal);
        String dateField = findByPostDate ? "postDate" : "timestamp";
        if (findByPostDate) {
            if (start != null)
                start = Util.floor(start);
            if (end != null)
                end = Util.ceil(end);
        }
        Criteria crit = session.createCriteria(GLTransaction.class).add(Restrictions.eq("journal", journal));
        crit.setProjection(Projections.rowCount());
        if (start != null && start.equals(end))
            crit.add(Restrictions.eq(dateField, start));
        else {
            if (start != null)
                crit.add(Restrictions.ge(dateField, start));
            if (end != null)
                crit.add(Restrictions.le(dateField, end));
        }
        if (searchString != null)
            crit.add(Restrictions.like("detail", "%" + searchString + "%"));
        return (Long) crit.uniqueResult();
    }

    /**
     * @return user object associated with this session.
     */
    public GLUser getUser() {
        return user;
    }

    /**
     * Current Balance for account in a given journal.
     * @param journal the journal.
     * @param acct the account.
     * @return current balance.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal getBalance(Journal journal, Account acct) throws HibernateException, GLException {
        return getBalances(journal, acct, null, true)[0];
    }

    /**
     * Current Balance for account in a given journal.
     * @param journal the journal.
     * @param acct the account.
     * @param layer the layers.
     * @return current balance.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal getBalance(Journal journal, Account acct, short layer)
            throws HibernateException, GLException {
        return getBalances(journal, acct, null, true, new short[] { layer }, 0L)[0];
    }

    /**
     * Current Balance for account in a given journal.
     * @param journal the journal.
     * @param acct the account.
     * @param layers the layers.
     * @return current balance.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal getBalance(Journal journal, Account acct, short[] layers)
            throws HibernateException, GLException {
        return getBalances(journal, acct, null, true, layers, 0L)[0];
    }

    /**
     * Current Balance for account in a given journal.
     * @param journal the journal.
     * @param acct the account.
     * @param layers comma separated list of layers
     * @return current balance.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal getBalance(Journal journal, Account acct, String layers)
            throws HibernateException, GLException {
        return getBalances(journal, acct, null, true, toLayers(layers), 0L)[0];
    }

    /**
     * Balance for account in a given journal in a given date.
     * @param journal the journal.
     * @param acct the account.
     * @param date date (inclusive).
     * @return balance at given date.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal getBalance(Journal journal, Account acct, Date date) throws HibernateException, GLException {
        return getBalances(journal, acct, date, true)[0];
    }

    /**
     * Balance for account in a given journal in a given date.
     * @param journal the journal.
     * @param acct the account.
     * @param date date (inclusive).
     * @param layer layer
     * @return balance at given date.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal getBalance(Journal journal, Account acct, Date date, short layer)
            throws HibernateException, GLException {
        return getBalances(journal, acct, date, true, new short[] { layer }, 0L)[0];
    }

    /**
     * Balance for account in a given journal in a given date.
     * @param journal the journal.
     * @param acct the account.
     * @param date date (inclusive).
     * @param layers layers
     * @return balance at given date.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal getBalance(Journal journal, Account acct, Date date, short[] layers)
            throws HibernateException, GLException {
        return getBalances(journal, acct, date, true, layers, 0L)[0];
    }

    /**
     * Get Both Balances at given date
     * @param journal the journal.
     * @param acct the account.
     * @param date date (inclusive).
     * @param inclusive either true or false
     * @return array of 2 BigDecimals with balance and entry count.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal[] getBalances(Journal journal, Account acct, Date date, boolean inclusive)
            throws HibernateException, GLException {
        return getBalances(journal, acct, date, inclusive, LAYER_ZERO, 0L);
    }

    /**
     * Get Both Balances at given date
     * @param journal the journal.
     * @param acct the account.
     * @param date date (inclusive).
     * @param inclusive either true or false
     * @param layers the layers
     * @param maxId maximum GLEntry ID to be considered in the query (if greater than zero)
     * @return array of 2 BigDecimals with balance and entry count.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public BigDecimal[] getBalances(Journal journal, Account acct, Date date, boolean inclusive, short[] layers,
            long maxId) throws HibernateException, GLException {
        checkPermission(GLPermission.READ, journal);
        BigDecimal balance[] = { ZERO, Z };
        if (acct.getChildren() != null) {
            if (acct.isChart()) {
                return getChartBalances(journal, (CompositeAccount) acct, date, inclusive, layers, maxId);
            }
            Iterator iter = acct.getChildren().iterator();
            while (iter.hasNext()) {
                Account a = (Account) iter.next();
                BigDecimal[] b = getBalances(journal, a, date, inclusive, layers, maxId);
                balance[0] = balance[0].add(b[0]);
                // session.evict (a); FIXME this conflicts with r251 (cascade=evict genearting a failed to lazily initialize a collection
            }
        } else if (acct.isFinalAccount()) {
            Criteria entryCrit = session.createCriteria(GLEntry.class).add(Restrictions.eq("account", acct))
                    .add(Restrictions.in("layer", toShortArray(layers)));
            if (maxId > 0L)
                entryCrit.add(Restrictions.le("id", maxId));

            Criteria txnCrit = entryCrit.createCriteria("transaction").add(Restrictions.eq("journal", journal));
            if (date != null) {
                if (inclusive) {
                    txnCrit.add(Restrictions.lt("postDate", Util.tomorrow(date)));
                } else {
                    date = Util.floor(date);
                    txnCrit.add(Restrictions.lt("postDate", date));
                }
                Checkpoint chkp = getRecentCheckpoint(journal, acct, date, inclusive, layers);
                if (chkp != null) {
                    balance[0] = chkp.getBalance();
                    txnCrit.add(Restrictions.gt("postDate", chkp.getDate()));
                }

            } else {
                BalanceCache bcache = getBalanceCache(journal, acct, layers);
                if (bcache != null) {
                    balance[0] = bcache.getBalance();
                    entryCrit.add(Restrictions.gt("id", bcache.getRef()));
                }
            }
            List l = txnCrit.list();
            balance[0] = applyEntries(balance[0], l);
            balance[1] = new BigDecimal(l.size()); // hint for checkpoint
        }
        return balance;
    }

    /**
     * AccountDetail for date range
     * @param journal the journal.
     * @param acct the account.
     * @param start date (inclusive).
     * @param end date (inclusive).
     * @return Account detail for given period.
     * @throws GLException if user doesn't have READ permission on this jounral.
     */
    public AccountDetail getAccountDetail(Journal journal, Account acct, Date start, Date end, short[] layers)
            throws HibernateException, GLException {
        checkPermission(GLPermission.READ);
        start = Util.floor(start);
        end = Util.ceil(end);

        Criteria crit = session.createCriteria(GLEntry.class);

        boolean hasChildren = false;
        if (acct instanceof CompositeAccount) {
            Disjunction dis = Restrictions.disjunction();
            for (Long l : getChildren(acct)) {
                hasChildren = true;
                dis.add(Restrictions.idEq(l));
            }
            if (hasChildren) {
                Criteria subCrit = crit.createCriteria(("account"));
                subCrit.add(dis);
            }
        }
        if (!hasChildren) {
            crit.add(Restrictions.eq("account", acct));
        }

        crit.add(Restrictions.in("layer", toShortArray(layers)));
        crit = crit.createCriteria("transaction").add(Restrictions.eq("journal", journal))
                .add(Restrictions.ge("postDate", start)).add(Restrictions.le("postDate", end));

        BigDecimal initialBalance[] = getBalances(journal, acct, start, false, layers, 0L);
        crit.addOrder(Order.asc("postDate"));
        crit.addOrder(Order.asc("timestamp"));
        crit.addOrder(Order.asc("id"));
        List entries = crit.list();
        // BigDecimal finalBalance = applyEntries (initialBalance[0], entries);

        return new AccountDetail(journal, acct, initialBalance[0], start, end, entries, layers);
    }

    /**
     * @param journal the journal.
     * @param acct the account.
     * @param date date (null for last checkpoint)
     * @param inclusive either true or false
     * @return Most recent check point for given date.
     * @throws GLException if user doesn't have CHECKPOINT permission on this jounral.
     */
    public Checkpoint getRecentCheckpoint(Journal journal, Account acct, Date date, boolean inclusive,
            short[] layers) throws HibernateException, GLException {
        checkPermission(GLPermission.CHECKPOINT, journal);

        Criteria crit = session.createCriteria(Checkpoint.class).add(Restrictions.eq("journal", journal))
                .add(Restrictions.eq("account", acct));

        if (layers != null)
            crit.add(Restrictions.eq("layers", layersToString(layers)));

        if (date != null) {
            if (inclusive)
                crit.add(Restrictions.le("date", date));
            else
                crit.add(Restrictions.lt("date", date));
        }
        crit.addOrder(Order.desc("date"));
        crit.setMaxResults(1);
        return (Checkpoint) crit.uniqueResult();
    }

    public BalanceCache getBalanceCache(Journal journal, Account acct, short[] layers)
            throws HibernateException, GLException {
        checkPermission(GLPermission.CHECKPOINT, journal);
        Criteria crit = session.createCriteria(BalanceCache.class).add(Restrictions.eq("journal", journal))
                .add(Restrictions.eq("account", acct));

        if (layers != null)
            crit.add(Restrictions.eq("layers", layersToString(layers)));

        crit.addOrder(Order.desc("ref"));
        crit.setMaxResults(1);
        return (BalanceCache) crit.uniqueResult();
    }

    /**
     * @param journal the Journal
     * @param acct the account
     * @param date checkpoint date (inclusive)
     * @param threshold minimum number of  GLEntries required to create a checkpoint
     * @throws GLException if user doesn't have CHECKPOINT permission on this jounral.
     */
    public void createCheckpoint(Journal journal, Account acct, Date date, int threshold)
            throws HibernateException, GLException {
        createCheckpoint(journal, acct, date, threshold, LAYER_ZERO);
    }

    /**
     * @param journal the Journal
     * @param acct the account
     * @param date checkpoint date (inclusive)
     * @param layers taken into account in this checkpoint
     * @param threshold minimum number of  GLEntries required to create a checkpoint
     * @throws GLException if user doesn't have CHECKPOINT permission on this jounral.
     */
    public void createCheckpoint(Journal journal, Account acct, Date date, int threshold, short[] layers)
            throws HibernateException, GLException {
        if (date == null)
            throw new GLException("Invalid checkpoint date");
        checkPermission(GLPermission.CHECKPOINT, journal);
        // Transaction tx = session.beginTransaction();
        session.buildLockRequest(LockOptions.UPGRADE).lock(journal);
        createCheckpoint0(journal, acct, date, threshold, layers);
        // tx.commit();
    }

    public BigDecimal createBalanceCache(Journal journal, Account acct, short[] layers)
            throws HibernateException, GLException {
        return createBalanceCache(journal, acct, layers, getMaxGLEntryId());
    }

    public BigDecimal createBalanceCache(Journal journal, Account acct, short[] layers, long maxId)
            throws HibernateException, GLException {
        BigDecimal balance = null;
        if (acct instanceof CompositeAccount) {
            balance = ZERO;
            Iterator iter = ((CompositeAccount) acct).getChildren().iterator();
            while (iter.hasNext()) {
                Account a = (Account) iter.next();
                balance = balance.add(createBalanceCache(journal, a, layers, maxId));
            }
        } else if (acct instanceof FinalAccount) {
            lock(journal, acct);
            balance = getBalances(journal, acct, null, true, layers, maxId)[0];
            BalanceCache c = getBalanceCache(journal, acct, layers);
            if (c == null) {
                c = new BalanceCache();
                c.setJournal(journal);
                c.setAccount(acct);
                c.setLayers(layersToString(layers));
            }
            if (maxId != c.getRef()) {
                c.setRef(maxId);
                c.setBalance(balance);
                session.saveOrUpdate(c);
            }
        }
        return balance;
    }

    /**
     * Lock a journal.
     * @param journal the journal.
     * @throws HibernateException on database errors.
     * @throws GLException if user doesn't have POST permission on this jounral.
     */
    public void lock(Journal journal) throws HibernateException, GLException {
        checkPermission(GLPermission.POST, journal);
        session.buildLockRequest(LockOptions.UPGRADE).lock(journal);
    }

    /**
     * Lock an account in a given journal.
     * @param journal the journal.
     * @param acct the account.
     * @throws GLException if user doesn't have POST permission on this jounral.
     * @throws HibernateException on database errors.
     */
    public void lock(Journal journal, Account acct) throws HibernateException, GLException {
        checkPermission(GLPermission.POST, journal);
        AccountLock lck = getLock(journal, acct);
    }

    /**
     * Open underlying Hibernate session.
     * @throws HibernateException
     */
    public synchronized Session open() throws HibernateException {
        return db.open();
    }

    /**
     * Close underlying Hibernate session.
     * @throws HibernateException
     */
    public synchronized void close() throws HibernateException {
        db.close();
    }

    /**
     * @return underlying Hibernate Session.
     */
    public Session session() {
        return db.session();
    }

    /**
     * Begin hibernate transaction.
     * @return new Transaction
     */
    public Transaction beginTransaction() throws HibernateException {
        return session.beginTransaction();
    }

    /**
     * Begin hibernate transaction.
     * @param timeout timeout in seconds
     * @return new Transaction
     */
    public Transaction beginTransaction(int timeout) throws HibernateException {
        Transaction tx = session.beginTransaction();
        if (timeout > 0)
            tx.setTimeout(timeout);
        return tx;
    }

    public GLUser getUser(String nick) throws HibernateException {
        return (GLUser) session.createCriteria(GLUser.class).add(Restrictions.eq("nick", nick)).uniqueResult();
    }

    /**
     * set a journal's lockDate
     * @param journal the Journal
     * @param lockDate the lock date.
     * @throws HibernateException on database errors.
     * @throws GLException if users doesn't have global READ permission.
     */
    public void setLockDate(Journal journal, Date lockDate) throws GLException, HibernateException {
        checkPermission(GLPermission.WRITE, journal);
        // Transaction tx = session.beginTransaction();
        session.buildLockRequest(LockOptions.UPGRADE).lock(journal);
        journal.setLockDate(lockDate);
        // tx.commit();
    }

    public void deleteBalanceCache(Journal journal, Account account, short[] layers) throws HibernateException {
        StringBuilder sb = new StringBuilder("delete BalanceCache where journal = :journal");
        if (account != null)
            sb.append(" and account = :account");
        if (layers != null)
            sb.append(" and layers = :layers");

        Query query = session.createQuery(sb.toString()).setEntity("journal", journal);
        if (account != null)
            query.setEntity("account", account);
        if (layers != null)
            query.setString("layers", layersToString(layers));

        query.executeUpdate();
    }

    public GLTransactionGroup createGroup(String name, List<GLTransaction> transactions) {
        GLTransactionGroup group = new GLTransactionGroup(name);
        Set txns = new HashSet();
        for (GLTransaction t : transactions)
            txns.add(t);
        group.setTransactions(txns);
        session().save(group);
        return group;
    }

    public GLTransactionGroup findTransactionGroup(String name) {
        Criteria crit = session.createCriteria(GLTransactionGroup.class).add(Restrictions.eq("name", name));
        crit.setMaxResults(1);
        return (GLTransactionGroup) crit.uniqueResult();
    }

    public BigDecimal getBalance(Journal journal, Account acct, GLTransactionGroup group, short[] layers)
            throws HibernateException, GLException {
        checkPermission(GLPermission.READ, journal);
        BigDecimal balance = ZERO;
        for (GLTransaction transaction : (Set<GLTransaction>) group.getTransactions()) {
            if (transaction.getJournal().equals(journal)) {
                for (GLEntry entry : (List<GLEntry>) transaction.getEntries()) {
                    if (acct.equals(entry.getAccount()) && entry.hasLayers(layers)) {
                        if (entry.isIncrease()) {
                            balance = balance.add(entry.getAmount());
                        } else if (entry.isDecrease()) {
                            balance = balance.subtract(entry.getAmount());
                        }
                    }
                }
            }
        }
        return balance;
    }

    // -----------------------------------------------------------------------
    // PUBLIC HELPERS 
    // -----------------------------------------------------------------------
    public short[] toLayers(String layers) {
        StringTokenizer st = new StringTokenizer(layers, ", ");
        short[] sa = new short[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++)
            sa[i] = Short.parseShort(st.nextToken());
        return sa;
    }

    // -----------------------------------------------------------------------
    // PRIVATE METHODS
    // -----------------------------------------------------------------------
    private AccountLock getLock(Journal journal, Account acct) throws HibernateException {
        AccountLock key = new AccountLock(journal, acct);
        AccountLock lck = (AccountLock) session.get(AccountLock.class, key, LockOptions.UPGRADE);
        if (lck == null)
            session.buildLockRequest(LockOptions.UPGRADE).lock(journal); // need a journal level lock
        lck = (AccountLock) session.get(AccountLock.class, key, LockOptions.UPGRADE); // try again
        if (lck == null) {
            session.save(lck = key);
            session.flush();
        }
        return lck;
    }

    private void createCheckpoint0(Journal journal, Account acct, Date date, int threshold, short[] layers)
            throws HibernateException, GLException {
        if (acct instanceof CompositeAccount) {
            Iterator iter = ((CompositeAccount) acct).getChildren().iterator();
            while (iter.hasNext()) {
                Account a = (Account) iter.next();
                createCheckpoint0(journal, a, date, threshold, layers);
            }
        } else if (acct instanceof FinalAccount) {
            Date sod = Util.floor(date); // sod = start of day
            invalidateCheckpoints(journal, new Account[] { acct }, sod, sod, layers);
            BigDecimal b[] = getBalances(journal, acct, date, true, layers, 0L);
            if (b[1].intValue() >= threshold) {
                Checkpoint c = new Checkpoint();
                c.setDate(date);
                c.setBalance(b[0]);
                c.setJournal(journal);
                c.setAccount(acct);
                c.setLayers(layersToString(layers));
                session.save(c);
            }
        }
    }

    private Account[] getAccounts(GLTransaction txn) {
        List list = txn.getEntries();
        Account[] accounts = new Account[list.size()];
        Iterator iter = list.iterator();
        for (int i = 0; iter.hasNext(); i++) {
            GLEntry entry = (GLEntry) iter.next();
            accounts[i] = entry.getAccount();
        }
        return accounts;
    }

    private List getAccountHierarchyIds(Account acct) throws GLException {
        if (acct == null)
            throw new GLException("Invalid entry - account is null");
        Account p = acct;
        List<Long> l = new ArrayList<Long>();
        while (p != null) {
            l.add(p.getId());
            p = p.getParent();
        }
        return l;
    }

    private void invalidateCheckpoints(GLTransaction txn) throws HibernateException {
        Account[] accounts = getAccounts(txn);
        invalidateCheckpoints(txn.getJournal(), accounts, txn.getPostDate(), null, null);
    }

    private void invalidateCheckpoints(Journal journal, Account[] accounts, Date start, Date end, short[] layers)
            throws HibernateException {
        Criteria crit = session.createCriteria(Checkpoint.class).add(Restrictions.eq("journal", journal));
        if (accounts.length > 0)
            crit = crit.add(Restrictions.in("account", accounts));

        if (layers != null)
            crit.add(Restrictions.eq("layers", layersToString(layers)));
        if (start.equals(end))
            crit.add(Restrictions.eq("date", start));
        else {
            crit.add(Restrictions.ge("date", start));
            if (end != null) {
                crit.add(Restrictions.le("date", end));
            }
        }
        Iterator iter = crit.list().iterator();
        while (iter.hasNext()) {
            Checkpoint cp = (Checkpoint) iter.next();
            session.delete(cp);
        }
        session.flush();
    }

    private BigDecimal applyEntries(BigDecimal balance, List entries) throws GLException {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            GLEntry entry = (GLEntry) iter.next();
            if (entry.isIncrease()) {
                balance = balance.add(entry.getAmount());
            } else if (entry.isDecrease()) {
                balance = balance.subtract(entry.getAmount());
            } else {
                throw new GLException(entry.toString() + " has invalid account type");
            }
        }
        return balance;
    }

    private Object getRuleImpl(String clazz) throws GLException {
        Object impl = ruleCache.get(clazz);
        if (impl == null) {
            synchronized (ruleCache) {
                impl = ruleCache.get(clazz);
                if (impl == null) {
                    try {
                        Class cls = Class.forName(clazz);
                        impl = cls.newInstance();
                        ruleCache.put(clazz, impl);
                    } catch (Exception e) {
                        throw new GLException("Invalid rule " + clazz, e);
                    }
                }
            }
        }
        return impl;
    }

    private void addRules(Map<String, Object> ruleMap, Journal journal, List acctHierarchy, int offset)
            throws HibernateException, GLException {
        Query q = session.createQuery(
                "from org.jpos.gl.RuleInfo where journal=:journal and account in (:accts) order by id");
        q.setParameter("journal", journal);
        q.setParameterList("accts", acctHierarchy, new LongType());
        q.setCacheable(true);
        q.setCacheRegion("rules");
        Iterator iter = q.iterate();

        while (iter.hasNext()) {
            RuleInfo ri = (RuleInfo) iter.next();
            RuleEntry k = new RuleEntry(ri, ri.getAccount());
            RuleEntry re = (RuleEntry) ruleMap.get(k.getKey());
            if (re == null)
                ruleMap.put(k.getKey(), re = k);

            re.addOffset(offset);
        }
    }

    private void applyRules(GLTransaction txn, Collection rules) throws HibernateException, GLException {
        Iterator iter = rules.iterator();
        while (iter.hasNext()) {
            RuleEntry re = (RuleEntry) iter.next();
            RuleInfo ri = re.getRuleInfo();
            JournalRule rule = (JournalRule) getRuleImpl(ri.getClazz());
            rule.check(this, txn, ri.getParam(), re.getAccount(), re.getOffsets(), ri.getLayerArray());
        }
    }

    private Collection getRules(GLTransaction txn) throws HibernateException, GLException {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        Journal journal = txn.getJournal();

        Query q = session
                .createQuery("from org.jpos.gl.RuleInfo where journal=:journal and account is null order by id");
        q.setParameter("journal", journal);
        Iterator iter = q.list().iterator();
        while (iter.hasNext()) {
            RuleInfo ri = (RuleInfo) iter.next();
            RuleEntry re = new RuleEntry(ri);
            map.put(re.getKey(), re);
        }
        iter = txn.getEntries().iterator();
        for (int i = 0; iter.hasNext(); i++) {
            GLEntry entry = (GLEntry) iter.next();
            addRules(map, journal, getAccountHierarchyIds(entry.getAccount()), i);
        }
        return map.values();
    }

    private BigDecimal[] getChartBalances(Journal journal, CompositeAccount acct, Date date, boolean inclusive,
            short[] layers, long maxId) throws HibernateException, GLException {
        BigDecimal balance[] = { ZERO, ZERO };
        Iterator iter = ((CompositeAccount) acct).getChildren().iterator();
        while (iter.hasNext()) {
            Account a = (Account) iter.next();
            BigDecimal[] b = getBalances(journal, a, date, inclusive, layers, maxId);
            if (a.isDebit()) {
                balance[0] = balance[0].add(b[0]);
                balance[1] = balance[1].add(b[1]);
            } else if (a.isCredit()) {
                balance[0] = balance[0].subtract(b[0]);
                balance[1] = balance[1].subtract(b[1]);
            } else {
                throw new GLException("Account " + a + " has wrong type");
            }
            // session.evict (a);  FIXME this conflicts with r251 (cascade=evict genearting a failed to lazily initialize a collection
        }
        return balance;
    }

    private Iterator findSummarizedGLEntries(Journal journal, Date start, Date end, boolean credit, short layer)
            throws HibernateException, GLException {
        StringBuffer qs = new StringBuffer(
                "select entry.account, sum(entry.amount)" + " from org.jpos.gl.GLEntry entry,"
                        + " org.jpos.gl.GLTransaction txn" + " where txn.id = entry.transaction"
                        + " and credit = :credit" + " and txn.journal = :journal" + " and entry.layer = :layer");
        boolean equalDate = start.equals(end);
        if (equalDate) {
            qs.append(" and txn.postDate = :date");
        } else {
            qs.append(" and txn.postDate >= :start");
            qs.append(" and txn.postDate <= :end");
        }
        qs.append(" group by entry.account");
        Query q = session.createQuery(qs.toString());
        q.setLong("journal", journal.getId());
        q.setParameter("credit", credit ? "Y" : "N");
        q.setShort("layer", layer);
        if (equalDate)
            q.setParameter("date", start);
        else {
            q.setParameter("start", start);
            q.setParameter("end", end);
        }
        return q.iterate();
    }

    private void deleteGLTransactions(Journal journal, Date start, Date end)
            throws HibernateException, GLException {
        boolean equalDate = start.equals(end);

        StringBuffer qs = new StringBuffer("from org.jpos.gl.GLTransaction where journal = :journal");
        if (equalDate) {
            qs.append(" and postDate = :date");
        } else {
            qs.append(" and postDate >= :start");
            qs.append(" and postDate <= :endDate");
        }
        Query q = session.createQuery(qs.toString());
        q.setLong("journal", journal.getId());
        if (equalDate)
            q.setParameter("date", start);
        else {
            q.setParameter("start", start);
            q.setParameter("endDate", end);
        }
        ScrollableResults sr = q.scroll(ScrollMode.FORWARD_ONLY);
        while (sr.next()) {
            session.delete(sr.get(0));
        }
    }

    private static Short[] toShortArray(short[] i) {
        if (i == null)
            return new Short[0];
        Short[] sa = new Short[i.length];
        for (int j = 0; j < i.length; j++)
            sa[j] = new Short(i[j]);
        return sa;
    }

    private String layersToString(short[] layers) {
        StringBuffer sb = new StringBuffer();
        Arrays.sort(layers);
        for (int i = 0; i < layers.length; i++) {
            if (i > 0)
                sb.append('.');
            sb.append(Short.toString(layers[i]));
        }
        return sb.toString();
    }

    private long getMaxGLEntryId() {
        Criteria crit = session.createCriteria(GLEntry.class);
        crit.addOrder(Order.desc("id"));
        crit.setMaxResults(1);
        GLEntry entry = (GLEntry) crit.uniqueResult();
        return entry != null ? entry.getId() : 0L;
    }

    private void recurseChildren(Account acct, List<Long> list) {
        for (Account a : acct.getChildren()) {
            if (a instanceof FinalAccount)
                list.add(a.getId());
            else
                recurseChildren(a, list);
        }
    }

    private List<Long> getChildren(Account acct) {
        List<Long> list = new ArrayList<Long>();
        recurseChildren(acct, list);
        return list;
    }

    public String toString() {
        return super.toString() + "[DB=" + db.toString() + "]";
    }

    /*
    private void dumpRules (Collection rules) {
    log.warn ("--- rules ---");
    Iterator iter = rules.iterator();
    while (iter.hasNext()) {
        log.warn (iter.next());
    }
    }
    */
}