at.treedb.db.Base.java Source code

Java tutorial

Introduction

Here is the source code for at.treedb.db.Base.java

Source

/*
* (C) Copyright 2014-2016 Peter Sauer (http://treedb.at/).
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 */

package at.treedb.db;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.openjpa.enhance.Reflection;
import org.apache.openjpa.persistence.OpenJPAPersistence;

import at.treedb.backup.ExportIface;
import at.treedb.ci.CI;
import at.treedb.ci.Image;
import at.treedb.ci.ImageDummy;
import at.treedb.domain.DBcategory;
import at.treedb.domain.Domain;
import at.treedb.i18n.Istring;
import at.treedb.i18n.IstringDummy;
import at.treedb.user.User;

/**
 * Abstract base class for persiting entities.
 * 
 * @author Peter Sauer
 * 
 */
@SuppressWarnings("serial")
// DOLR: 29.11.2015
@MappedSuperclass
@DBindex(columnList = "histId")
public abstract class Base implements Serializable, HistorizationIface, ExportIface {
    // internal list for tracking callbacks
    private static HashMap<Class<? extends Base>, HashSet<String>> callbackUpdateFields = new HashMap<Class<? extends Base>, HashSet<String>>();
    // data base ID
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    // domain ID
    @DBkey(value = Domain.class)
    protected int domain;
    // timestamps for creation, modification and deletion
    @Temporal(TemporalType.TIMESTAMP)
    private Date creationTime;
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModified;
    @Temporal(TemporalType.TIMESTAMP)
    private Date deletionDate;
    // historization status
    @Enumerated(EnumType.ORDINAL)
    private STATUS status = STATUS.ACTIVE;
    // DB ID of the first created entity
    private int histId;
    private int version;
    // internal entity version counter - managed by the persistence layer
    // transactions and concurrency tracking
    @Version
    private int dbVersion;
    // user tracking for creation & modification of entities
    @DBkey(value = User.class)
    private int createdBy;
    @DBkey(value = User.class)
    private int modifiedBy;

    private static Random random = new Random();;

    /**
     * Stores a pair class/field for tracking callbacks for fields.
     * 
     * @param clazz
     *            class to be tracked for updates
     * @param field
     *            name of the field
     */
    static public void addCallbackUpdateField(Class<? extends Base> clazz, String field) {
        HashSet<String> set = callbackUpdateFields.get(clazz);
        if (set == null) {
            set = new HashSet<String>();
            callbackUpdateFields.put(clazz, set);
        }
        set.add(field);
    }

    /**
     * Invokes a callback before updating the entity.
     * 
     * @param dao
     *            {@code DAOiface} data access object
     * @param user
     *            user {@code User} who updates the entity
     * @param map
     *            map containing the data
     * @param info
     *            optional context object
     * @return {@code true} if a update takes place, {@code false} if not
     * @throws Exception
     */
    private boolean invokeCallbackUpdate(DAOiface dao, User user, UpdateMap map, Object context) throws Exception {
        HashSet<String> set = callbackUpdateFields.get(this.getClass());
        if (set == null) {
            return false;
        }
        for (Enum<?> e : map.getMap().keySet()) {
            if (set.contains(e.name())) {
                this.callbackUpdate(dao, user, map, context);
                return true;
            }
        }
        return false;
    }

    /**
     * Checks data against optional constraints.
     * 
     * @param dao
     *            {@code DAOiface} data access object
     * @param map
     *            map containing the data
     * @return optional context object
     * @throws Exception
     */
    public Object checkConstraints(DAOiface dao, UpdateMap map) throws Exception {
        return null;
    }

    /**
     * Optional method/callback before persisting data e.g. used for setting the
     * internal ID according a rule.
     */
    protected void callbackBeforeSave() {
    }

    /**
     * Optional method/callback before updating data.
     */
    protected void callbackUpdate(DAOiface dao, User user, UpdateMap map, Object context) throws Exception {
    }

    /**
     * Optional callback after loading the entity. e.g. used for setting
     * transient data fields
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @throws Exception
     */
    public void callbackAfterLoad(DAOiface dao) throws Exception {
    }

    /**
     * Checks if a callback after loading entity is necessary.
     * 
     * @return {@code true} if {@code callbackAfterLoad()} is invoked,
     *         {@code false} if not
     */
    public boolean isCallbackAfterLoad() {
        return false;
    }

    /**
     * Returns the proprietary DB ID of the entity.
     * 
     * @return DB ID
     */
    @Override
    public int getDBid() {
        return id;
    }

    /**
     * Returns a composed unique DB id consisting of the proprietary DB ID and
     * the class ID.
     * 
     * @return composed unique DB id
     */
    public long getUniqueDBid() {
        return ((long) getCID().ordinal() << 32) | (long) histId;
    }

    /*
     * histId should be unique and can be used as a hash value
     */
    @Override
    public int hashCode() {
        return histId;
    }

    /*
     * see above comment
     */
    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        // ensure that only objects of the same class are compared
        // the histId is only unique over the entities of a class (Hibernate vs.
        // JPA)
        if (!(this.getClass().equals(o.getClass()))) {
            return false;
        }
        return ((Base) o).histId == histId;
    }

    /**
     * Sets the proprietary DB ID of the entity.
     * 
     * @param id
     *            DB ID
     */
    @Override
    public void setDBid(int id) {
        this.id = id;
    }

    /**
     * Returns the version of the entity. Each historization (=update)
     * increments the version counter.
     * 
     * @return version counter
     */
    @Override
    public int getVersion() {
        return version;
    }

    /**
     * Increments the version counter.
     */
    @Override
    public void incVersion() {
        ++version;
    }

    /**
     * Sets the historization version counter.
     * 
     * @param version
     *            version counter
     */
    @Override
    public void setVersion(int version) {
        this.version = version;
    }

    /**
     * Sets the creation time stamp of the entity.
     * 
     * @param timestamp
     *            creation time
     */
    public void setCreationTime(Date timestamp) {
        this.creationTime = timestamp;
    }

    /**
     * Returns the creation time stamp of the entity.
     * 
     * @return creation time
     */
    public Date getCreationTime() {
        return creationTime;
    }

    /**
     * Sets the last modification time stamp of the entity.
     * 
     * @param timestamp
     *            modification time
     */
    public void setLastModified(Date timestamp) {
        lastModified = timestamp;
    }

    /**
     * Returns the last modification time stamp of the entity.
     * 
     * @return last modification time
     */
    public Date getLastModified() {
        return lastModified;
    }

    /**
     * Sets the deletion time stamp of the entity.
     * 
     * @param delDate
     *            deletion time
     */
    public void setDeletionDate(Date delDate) {
        deletionDate = delDate;

    }

    /**
     * Returns the deletion time stamp of the entity.
     * 
     * @return deletion time
     */
    public Date getDeletionDate() {
        return deletionDate;
    }

    /**
     * Sets the historization status of the entity.
     * 
     * @param status
     *            historization status
     */
    public void setHistStatus(STATUS status) {
        this.status = status;
    }

    /**
     * Returns the historization status of the entity.
     * 
     * @return historization status
     */
    public STATUS getHistStatus() {
        return status;
    }

    /**
     * Sets the historization ID of the entity. This value is equal to the DB ID
     * of the first version of the entity.
     * 
     * @param histId
     *            historization ID
     */
    public void setHistId(int histId) {
        this.histId = histId;
    }

    /**
     * Returns the historization ID of the entity.
     * 
     * @return historization ID
     */
    public int getHistId() {
        return histId;
    }

    /**
     * Sets the creator of this entity.
     * 
     * @param createdBy
     *            {@code User} ID
     */
    public void setCreatedBy(@DBkey(value = User.class) int createdBy) {
        this.createdBy = createdBy;
    }

    /**
     * Returns the creator of this entity.
     * 
     * @return {@code User} ID
     */
    public @DBkey(value = User.class) int getCreatedBy() {
        return createdBy;
    }

    /**
     * Sets the user who made the last modification of this entity.
     * 
     * @param modifiedBy
     *            {@code User} ID
     */
    public void setModifiedBy(@DBkey(value = User.class) int modifiedBy) {
        this.modifiedBy = modifiedBy;
    }

    /**
     * Returns the user who has make the last modification of this entity.
     * 
     * @return {@code User} ID
     */
    public @DBkey(value = User.class) int getModifiedBy() {
        return modifiedBy;
    }

    /**
     * Returns the transaction version of this entity - managed by the
     * transaction layer.
     * 
     * @return transaction version
     */
    public int getTransactionVersion() {
        return dbVersion;
    }

    /**
     * Sets the transaction version of the entity to 0. Used internally for
     * operations like cloning objects.
     */
    public void resetTransactionVersion() {
        this.dbVersion = 0;
    }

    /**
     * Sets the {@code Domain}.
     * 
     * @param domain
     *            {@code Domain} of the entity
     */
    public void setDomain(@DBkey(value = Domain.class) int domain) {
        this.domain = domain;
    }

    /**
     * Returns the {@code Domain} ID.
     * 
     * @return {@code Domain} of the entity
     */
    public @DBkey(value = Domain.class) int getDomain() {
        return domain;
    }

    /**
     * Loads an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param clazz
     *            entity class to be loaded
     * @param id
     *            base class ID
     * @param date
     *            optional temporal bound
     * @return {@code Base} entity
     * @throws Exception
     */
    protected static Base load(DAOiface dao, Class<? extends Base> clazz, @DBkey(value = Base.class) int id,
            Date date) throws Exception {
        return load(dao, clazz, id, date, false);
    }

    /**
     * Loads an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param clazz
     *            entity class to be loaded
     * @param id
     *            base class ID
     * @return {@code Base} entity
     * @throws Exception
     */
    public static Base load(DAOiface dao, Class<? extends Base> clazz, @DBkey(value = Base.class) int id)
            throws Exception {
        return load(dao, clazz, id, null, false);
    }

    /**
     * Loads an entity without binary data.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param clazz
     *            entity class to be loaded
     * @param id
     *            base class ID
     * @param date
     *            optional temporal bound
     * @return {@code Base} entity
     * @throws Exception
     */
    protected static Base loadLazy(DAOiface dao, Class<? extends Base> clazz, @DBkey(value = Base.class) int id,
            Date date) throws Exception {
        return load(dao, clazz, id, date, true);
    }

    /**
     * Loads an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param clazz
     *            entity class to be loaded
     * @param id
     *            base class ID
     * @param date
     *            optional temporal bound
     * @param lazy
     *            {@code true} for lazy loading
     * @return {@code Base} entity
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    protected static Base load(DAOiface dao, Class<? extends Base> clazz, @DBkey(value = Base.class) int id,
            Date date, boolean lazy) throws Exception {
        Base base = null;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            List<Base> list = null;
            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("id", id);
            String className = clazz.getSimpleName();
            if (date == null) {
                map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
                list = (List<Base>) dao.query(
                        "select i from " + className + " i where i.histId = :id and i.status = :status", map);
            } else {
                // load all entities
                map.put("date", date);
                list = (List<Base>) dao.query("select i from " + className
                        + " i where i.histId = :id and i.lastModified < :date and (i.deletionDate is null or i.deletionDate > :date) order by i.version desc",
                        0, 1, map);
            }
            if (list.size() == 1) {
                base = list.get(0);
                if (lazy) {
                    // callback for loading the binary data
                    base.callbackAfterLoad(dao);
                }
            } else {
                if (list.size() > 1) {
                    throw new Exception("Base.load(): Entity/DB ID is not unique!");
                }
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return base;
    }

    /**
     * Loads all entities of a class.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param clazz
     *            class name
     * @param date
     *            optional temporal bound
     * @param callbackAfterLoad
     *            calls the overwritten method {@code callbackAfterLoad}
     * @return list entity list
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static List<? extends Base> loadEntities(DAOiface dao, Class<? extends Base> clazz, Date date,
            boolean callbackAfterLoad) throws Exception {
        List<? extends Base> list = null;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }

            String className = clazz.getSimpleName();
            HashMap<String, Object> map = new HashMap<String, Object>();
            if (date == null) {
                // load all active entities
                map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
                list = (List<? extends Base>) dao
                        .query("select i from " + className + " i where i.status = :status", map);
            } else {
                // load all entities with a temporal bound
                map.put("date", date);
                list = (List<? extends Base>) dao.query("select i from " + className
                        + " i where i.lastModified < :date and (i.deletionDate is null or i.deletionDate > :date) order by i.version desc",
                        0, 1, map);
            }
            if (callbackAfterLoad && !list.isEmpty()) {
                for (Base b : list) {
                    b.callbackAfterLoad(dao);
                }
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            dao.rollback();
            throw e;
        }
        return list;
    }

    /**
     * Loads entities of a class.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param clazz
     *            class name
     * @param domain
     *            {@code Domain} of the entity
     * @param crit
     *            search criteria
     * @param date
     *            temporal bound
     * @return list entity list
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static List<? extends Base> loadEntities(DAOiface dao, Class<? extends Base> clazz, int domain,
            SearchCriteria crit, Date date) throws Exception {
        List<Base> list = null;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("domain", domain);
            String criteria = "";
            if (crit != null) {
                String critName = crit.getEnumValue().name();
                criteria += " and data." + critName + " " + crit.getOperator().toString() + " :" + critName;
                map.put(critName, crit.getData());
            }
            String className = clazz.getSimpleName();
            // load only active entities
            if (date == null) {
                map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
                list = (List<Base>) dao.query("select data from " + className
                        + " data where data.domain = :domain and data.status = :status" + criteria, map);
            } else {
                map.put("date", date);
                list = (List<Base>) dao.query("select data from " + className
                        + " data where data.domain = :domain and data.lastModified < :date and (data.deletionDate = null or data.deletionDate > :date)"
                        + criteria + " order by data.version having max(data.version)", map);
            }
            if (!list.isEmpty() && list.get(0).isCallbackAfterLoad()) {
                for (Base b : list) {
                    b.callbackAfterLoad(dao);
                }
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return list;
    }

    /**
     * Loads entities of a class.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param clazz
     *            class name
     * @param domain
     *            {@code Domain} of the entity
     * @param crit
     *            search criteria
     * @param date
     *            temporal bound
     * @return list entity list
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static List<? extends Base> loadEntities(DAOiface dao, Class<? extends Base> clazz, SearchCriteria crit,
            Date date) throws Exception {
        List<Base> list = null;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            HashMap<String, Object> map = new HashMap<String, Object>();
            String criteria = "";
            if (crit != null) {
                String critName = crit.getEnumValue().name();
                criteria += " and data." + critName + " " + crit.getOperator().toString() + " :" + critName;
                map.put(critName, crit.getData());
            }
            String className = clazz.getSimpleName();
            // load only active entities
            if (date == null) {
                map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
                list = (List<Base>) dao.query(
                        "select data from " + className + " data where data.status = :status" + criteria, map);
            } else {
                map.put("date", date);
                list = (List<Base>) dao.query("select data from " + className
                        + " data where data.lastModified < :date and (data.deletionDate = null or data.deletionDate > :date)"
                        + criteria + " order by data.version having max(data.version)", map);
            }
            if (!list.isEmpty() && list.get(0).isCallbackAfterLoad()) {
                for (Base b : list) {
                    b.callbackAfterLoad(dao);
                }
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return list;
    }

    /**
     * Loads entities of a class.
     * 
     * @param dao
     * @param domain
     * @param clazz
     * @param crit
     * @param limit
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static List<? extends Base> loadEntities(DAOiface dao, Domain domain, Class<? extends Base> clazz,
            SearchCriteria crit, SearchLimit limit) throws Exception {
        List<Base> list = null;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            HashMap<String, Object> map = new HashMap<String, Object>();

            String criteria = "";
            if (crit != null) {
                String critName = crit.getEnumValue().name();
                criteria += " and data." + critName + " " + crit.getOperator().toString() + " :" + critName;
                map.put(critName, crit.getData());
            }
            String dom = "";
            if (domain != null) {
                dom = " and data.domain = :domain";
                map.put("domain", domain.getHistId());
            }
            String className = clazz.getSimpleName();
            // load only active entities
            map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
            list = (List<Base>) dao.query(
                    "select data from " + className + " data where data.status = :status" + dom + criteria
                            + " order by data.creationTime DESC",
                    limit.getFirstResult(), limit.getMaxResults(), map);

            if (!list.isEmpty() && list.get(0).isCallbackAfterLoad()) {
                for (Base b : list) {
                    b.callbackAfterLoad(dao);
                }
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return list;
    }

    /**
     * 
     * @param dao
     * @param domain
     * @param clazz
     * @param crit
     * @param start
     * @param end
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static List<? extends Base> loadEntities(DAOiface dao, Domain domain, Class<? extends Base> clazz,
            SearchCriteria crit, Date start, Date end) throws Exception {
        List<Base> list = null;
        if (start == null || end == null) {
            throw new Exception("Base.loadEntities(): temporal bound can not be null");
        }
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            HashMap<String, Object> map = new HashMap<String, Object>();

            String criteria = "";
            if (crit != null) {
                String critName = crit.getEnumValue().name();
                criteria += " and data." + critName + " " + crit.getOperator().toString() + " :" + critName;
                map.put(critName, crit.getData());
            }
            String dom = "";
            if (domain != null) {
                dom = " and data.domain = :domain";
                map.put("domain", domain.getHistId());
            }
            String className = clazz.getSimpleName();
            // load only active entities
            map.put("start", start);
            map.put("end", end);
            map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
            String time = " and (data.creationTime >= :start and data.creationTime <= :end)";
            list = (List<Base>) dao.query(
                    "select data from " + className + " data where data.status = :status" + dom + criteria + time,
                    map);

            if (!list.isEmpty() && list.get(0).isCallbackAfterLoad()) {
                for (Base b : list) {
                    b.callbackAfterLoad(dao);
                }
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return list;
    }

    /**
     * 
     * @param dao
     * @param clazz
     * @param crit
     * @param date
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static List<? extends Base> loadEntities(DAOiface dao, Class<? extends Base> clazz,
            SearchCriteria[] crit, Date date) throws Exception {
        List<Base> list = null;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            HashMap<String, Object> map = new HashMap<String, Object>();
            String criteria = "";
            if (crit != null) {
                for (SearchCriteria c : crit) {
                    String critName = c.getEnumValue().name();
                    criteria += " and data." + critName + " " + c.getOperator().toString() + " :" + critName;
                    map.put(critName, c.getData());
                }

            }
            String className = clazz.getSimpleName();
            // load only active entities
            if (date == null) {
                map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
                list = (List<Base>) dao.query(
                        "select data from " + className + " data where data.status = :status" + criteria, map);
            } else {
                map.put("date", date);
                list = (List<Base>) dao.query("select data from " + className
                        + " data where data.lastModified < :date and (data.deletionDate = null or data.deletionDate > :date)"
                        + criteria + " order by data.version having max(data.version)", map);
            }
            if (!list.isEmpty() && list.get(0).isCallbackAfterLoad()) {
                for (Base b : list) {
                    b.callbackAfterLoad(dao);
                }
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return list;
    }

    /**
     * Saves an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param domain
     *            domain {@Domain) of the entity.
     * @param user
     *            user {@code User} who deletes the entity
     * @param base
     *            entity to be saved
     * @throws Exception
     */
    protected static void save(DAOiface dao, Domain domain, User user, Base base) throws Exception {
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            // perform some checks
            base.checkConstraints(dao, null);

            Date d = new Date();
            base.setCreationTime(d);
            base.setLastModified(d);
            if (domain != null) {
                base.setDomain(domain.getHistId());
            }

            if (user != null) {
                base.setCreatedBy(user.getHistId());
                base.setModifiedBy(user.getHistId());
            }
            // save entity to get an ID
            dao.saveAndFlushIfJPA(base);
            // optional callback for persisting data
            base.callbackBeforeSave();

            // historization ID = DB ID
            base.setHistId(base.getDBid());
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
    }

    /**
     * Help method for restoring an entity from the backup. This method resets
     * all internal object IDs.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param base
     *            restored object
     * @throws Exception
     */
    public static void restore(DAOiface dao, Base base) throws Exception {
        // reset internal values
        base.id = base.dbVersion = 0;
        // save entity to get an ID
        dao.saveAndFlushIfJPA(base);
    }

    /**
     * Deletes an entity.
     * 
     * @param user
     *            {@code User} who deletes the entity
     * 
     * @param base
     *            base entity to be deleted
     * @param dbDelete
     *            {@code true} for deleting the entity form DB, {@code false}
     *            historization delete
     * 
     * @throws Exception
     */
    static protected void delete(User user, Base base, boolean dbDelete) throws Exception {
        delete(null, user, base, 0, null, dbDelete);
    }

    /**
     * Deletes an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * 
     * @param user
     *            {@code User} who deletes the entity.
     * 
     * @param base
     *            base entity to be deleted
     * @param dbDelete
     *            {@code true} for deleting the entity form DB, {@code false}
     *            historization deletion
     * 
     * @throws Exception
     */
    static protected void delete(DAOiface dao, User user, Base base, boolean dbDelete) throws Exception {
        delete(dao, user, base, 0, null, dbDelete);
    }

    /**
     * Deletes an entity.
     * 
     * @param user
     *            {@code User} who deletes the entity.
     * @param id
     *            ID of the entity
     * @param clazz
     *            class of the entity
     * @param dbDelete
     *            {@code true} for deleting the entity form DB, {@code false}
     *            historization deletion
     * @return {@code true} if the entity exits and deletion was successful,
     *         {@code false} if the entity ID doesn't exist
     * @throws Exception
     */
    static protected boolean delete(User user, @DBkey(value = Base.class) int id, Class<? extends Base> clazz,
            boolean dbDelete) throws Exception {
        return delete(null, user, null, id, clazz, dbDelete);
    }

    /**
     * Deletes an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param user
     *            {@code User} who deletes the entity.
     * @param id
     *            ID of the entity
     * @param clazz
     *            class of the entity
     * @param dbDelete
     *            {@code true} for deleting the entity form DB, {@code false}
     *            historization deletion
     * @return {@code true} if the entity exits and deletion was successful,
     *         {@code false} if the entity ID doesn't exist
     * @throws Exception
     */
    static protected boolean delete(DAOiface dao, User user, @DBkey(value = Base.class) int id,
            Class<? extends Base> clazz, boolean dbDelete) throws Exception {
        return delete(dao, user, null, id, clazz, dbDelete);
    }

    /**
     * Deletes an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * 
     * @param user
     *            user {@code User} who deletes an entity.
     * 
     * @param base
     *            entity to be deleted, if {@code null} the parameter entity ID
     *            will be used for the entity deletion
     * 
     * @param id
     *            ID of the entity, if the entity is {@code null}
     * 
     * @param clazz
     *            class information, if the entity is given by its id
     * @return {@code true} if the entity exits and deletion was successful,
     *         {@code false} if the entity ID doesn't exist
     * @throws Exception
     */
    static private boolean delete(DAOiface dao, User user, Base base, @DBkey(value = Base.class) int id,
            Class<? extends Base> clazz, boolean dbDelete) throws Exception {
        if (!dbDelete && base != null && base.getHistStatus() != HistorizationIface.STATUS.ACTIVE) {
            throw new Exception("User.delete(): Deleting historic entities isn't allowed!");
        }
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        Date d = new Date();
        boolean deleted = false;
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            if (base == null) {
                if (clazz == null) {
                    throw new Exception("Base.delete(): parameter clazz is null!");
                }
                base = dao.get(clazz, id);
            }
            if (base != null) {

                base.setHistStatus(STATUS.DELETED);
                base.setDeletionDate(d);
                if (user != null) {
                    base.setModifiedBy(user.getHistId());
                }
                dao.update(base);

                deleted = true;
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }

        return deleted;
    }

    /**
     * Updates an entity.
     * 
     * @param user
     *            user who updates an entity
     * 
     * @param base
     *            entity which should be updated
     * @param map
     *            map containing all changes
     * @throws Exception
     */
    public static void update(User user, @DBkey(value = Base.class) Base base, UpdateMap map) throws Exception {
        update(null, user, base, 0, null, map);
    }

    /**
     * Updates an entity.
     * 
     * @param user
     *            {@code User} who updates an entity
     * 
     * @param id
     *            entity ID which should be updated
     * @param clazz
     *            class information, if the entity is given by its ID
     * @param map
     *            map containing all changes
     * @throws Exception
     */
    public static void update(User user, @DBkey(value = Base.class) int id, Class<? extends Base> clazz,
            UpdateMap map) throws Exception {
        update(null, user, null, id, clazz, map);
    }

    /**
     * Updates an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * 
     * @param user
     *            {@code User} who updates an entity
     * 
     * @param base
     *            entity which should be updated
     * 
     * @param map
     *            map containing all changes
     * @throws Exception
     */
    public static void update(DAOiface dao, User user, Base base, UpdateMap map) throws Exception {
        update(dao, user, base, 0, null, map);
    }

    /**
     * Updates an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * 
     * @param user
     *            {@code User} who updates an entity
     * 
     * @param id
     *            entity ID which should be updated
     * @param clazz
     *            class information, if the entity is given by its ID
     * @param map
     *            map containing all changes
     * @throws Exception
     */
    public static void update(DAOiface dao, User user, @DBkey(value = Base.class) int id,
            Class<? extends Base> clazz, UpdateMap map) throws Exception {
        update(dao, user, null, id, clazz, map);
    }

    /**
     * Updates an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * 
     * @param user
     *            {@code User} who updates an entity
     * 
     * @param base
     *            entity which should be updated
     * @param id
     *            entity ID which should be updated
     * @param clazz
     *            class information, if the entity is given by its id
     * @param map
     *            map containing the changes
     * @throws Exception
     */
    private static void update(DAOiface dao, User user, Base base, @DBkey(value = Base.class) int id,
            Class<? extends Base> clazz, UpdateMap map) throws Exception {
        if (base != null && base.getHistStatus() != HistorizationIface.STATUS.ACTIVE) {
            throw new Exception("User.update(): Updating historic entities isn't allowed!");
        }
        if (base != null) {
            base.check(map);
        }
        if (map.size() == 0) {
            return;
        }
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            if (base == null) {
                if (clazz == null) {
                    throw new Exception("Base.update(): parameter clazz is null!");
                }
                base = dao.get(clazz, id);
                base.check(map);
            }
            Object info = null;
            if (map.size() > 0) {
                // update the entity?
                if (map.isEntityUpdate()) {
                    info = base.checkConstraints(dao, map);
                    synchronized (base) {
                        Base copy = (Base) base.clone();
                        if (copy.getHistStatus() == STATUS.ACTIVE) {
                            copy.setHistStatus(STATUS.UPDATED);
                        }
                        copy.setDBid(0);
                        copy.resetTransactionVersion();

                        dao.save(copy);

                        if (user != null) {
                            base.setModifiedBy(user.getHistId());
                        }
                        if (dao.getPersistenceLayer() == at.treedb.db.DAOiface.PERSISTENCE_LAYER.JPA) {
                            // the JPA way
                            // 1.) update the entity in memory
                            base.update(dao, user, map);
                            // 2.) re-load the entity
                            base = dao.get(base.getClass(), base.getDBid());
                        }
                        // 3.) update the entity
                        base.update(dao, user, map);
                        base.incVersion();
                        base.setLastModified(new Date());
                        dao.update(base);
                    }
                } else {
                    // update contains only referenced data types (e.g. Istring
                    // or
                    // Image)
                    base.update(dao, user, map);
                }
                base.invokeCallbackUpdate(dao, user, map, info);

            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
    }

    /**
     * Search filter
     * 
     * @author Peter Sauer
     * 
     */
    public enum Search {

        /**
         * case sensitive search
         */
        CASE_SENSITIVE,
        /**
         * include historic data
         */
        HISTORIC,
        /**
         * exact search
         */
        EQUALS,
        /**
         * result set limitation
         * 
         */
        LIMIT;

    }

    /**
     * Searches a value inside the fields of an entity.
     * 
     * @param domain
     *            {@code Domain} of the entity
     * @param clazz
     *            class to be searched
     * @param fields
     *            fields to be searched
     * @param value
     *            search pattern
     * @param criteria
     *            additional search criteria
     * @param flags
     *            search filter
     * @param callbackAfterLoad
     *            calls the overwritten method {@code callbackAfterLoad}
     * @return list of {@code Base} entities
     * @throws Exception
     */
    protected static List<Base> search(Domain domain, Class<? extends Base> clazz, EnumSet<?> fields, String value,
            SearchCriteria[] criteria, EnumSet<Search> flags, SearchLimit limit, boolean callbackAfterLoad)
            throws Exception {
        return search(null, domain, clazz, fields, value, criteria, flags, limit, callbackAfterLoad);
    }

    /**
     * Searches a value inside the fields of an entity.
     * 
     * @param domain
     *            {@code Domain} of the entity
     * @param clazz
     *            class to be searched
     * @param fields
     *            fields to be searched
     * @param value
     *            search pattern
     * @param criteria
     *            additional search criteria
     * @param flags
     *            search filter
     * @param callbackAfterLoad
     *            calls the overwritten method {@code callbackAfterLoad}
     * @return list of {@code Base} entities
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static List<Base> search(DAOiface dao, Domain domain, Class<? extends Base> clazz, EnumSet<?> fields,
            String value, SearchCriteria[] criteria, EnumSet<Search> flags, SearchLimit limit,
            boolean callbackAfterLoad) throws Exception {
        List<Base> list = null;
        boolean isDAOlocale = false;
        try {
            if (dao == null) {
                dao = DAO.getDAO();
                isDAOlocale = true;
                dao.beginTransaction();
            }
            StringBuffer buf = new StringBuffer();

            HashMap<String, Object> map = new HashMap<String, Object>();
            boolean caseSenstive = false;
            if (flags != null && flags.contains(Search.CASE_SENSITIVE)) {
                caseSenstive = true;
            } else {
                value = value.toUpperCase();
            }
            map.put("value", value);
            buf.append("select i from ");
            buf.append(clazz.getSimpleName());
            buf.append(" i where ");

            if (flags == null || !flags.contains(Search.HISTORIC)) {
                buf.append("i.status = :status and ");
                map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
            }

            if (domain != null) {
                buf.append("i.domain = :domain and ");
                map.put("domain", domain.getHistId());
            }

            if (criteria != null) {
                for (SearchCriteria crit : criteria) {
                    String critName = crit.getEnumValue().name();
                    buf.append(" i.");
                    buf.append(critName);
                    buf.append(" ");
                    buf.append(crit.getOperator().toString());
                    buf.append(" :");
                    buf.append(critName);
                    buf.append(" and");
                    map.put(critName, crit.getData());
                }
            }

            buf.append(" (");
            Iterator<?> iter = fields.iterator();

            int index = 0;
            while (iter.hasNext()) {
                if (index > 0) {
                    buf.append(" OR ");
                }
                if (!caseSenstive) {
                    buf.append("UPPER(");
                }
                buf.append("i.");
                buf.append(((Enum<?>) iter.next()).name());
                if (!caseSenstive) {
                    buf.append(")");
                }
                if (flags != null && flags.contains(Search.EQUALS)) {
                    buf.append(" = ");
                } else {
                    buf.append(" like ");
                }
                buf.append(":value");
                ++index;
            }

            buf.append(")");

            if (flags != null && flags.contains(Search.HISTORIC)) {
                buf.append(" ORDER BY i.version DESC");
            }
            if (flags != null && flags.contains(Search.LIMIT)) {
                list = (List<Base>) dao.query(buf.toString(), limit.getFirstResult(), limit.getMaxResults(), map);
            } else {
                list = (List<Base>) dao.query(buf.toString(), map);
            }
            if (callbackAfterLoad && !list.isEmpty()) {
                for (Base b : list) {
                    b.callbackAfterLoad(dao);
                }
            }
            if (isDAOlocale) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            dao.rollback();
            throw e;
        }
        return list;
    }

    /**
     * Checks the map containing the changes. Redundant update entries will be
     * removed.
     * 
     * @param map
     *            map containing the changes
     * @throws Exception
     */
    protected void check(UpdateMap map) throws Exception {
        Class<?> c = this.getClass();
        Enum<?>[] list = map.getMap().keySet().toArray(new Enum[map.getMap().keySet().size()]);
        for (Enum<?> field : list) {
            Update u = map.get(field);
            Field f;
            try {
                f = c.getDeclaredField(field.name());
            } catch (java.lang.NoSuchFieldException e) {
                f = c.getSuperclass().getDeclaredField(field.name());
            }
            f.setAccessible(true);
            switch (u.getType()) {
            case STRING:
                String value = u.getString();
                String s = (String) f.get(this);
                if ((value == null && s == null) || (s != null && value != null && s.equals(value))) {
                    map.remove(field);
                }
                break;
            case DOUBLE:
                if (u.getDouble() == (Double) f.get(this)) {
                    map.remove(field);
                }
                break;
            case LONG:
                if (u.getLong() == (Long) f.get(this)) {
                    map.remove(field);
                }
                break;
            case INT:
                if (u.getInt() == (Integer) f.get(this)) {
                    map.remove(field);
                }
                break;
            case BINARY:
                byte[] a = (byte[]) f.get(this);
                byte[] b = u.getBinary();
                if ((a == null && b == null) || (a != null && b != null && Arrays.equals(a, b))) {
                    map.remove(field);
                }
                break;
            case BOOLEAN:
                if (u.getBoolean() == (Boolean) f.get(this)) {
                    map.remove(field);
                }
                break;
            case DATE:
                Date dvalue = u.getDate();
                Date d = (Date) f.get(this);
                if ((dvalue == null && d == null) || (d != null && dvalue != null && d.equals(dvalue))) {
                    map.remove(field);
                }
                break;
            case ENUM:
                if (u.getEnum() == (Enum<?>) f.get(this)) {
                    map.remove(field);
                }
                break;
            case BIGDECIMAL:
                if (u.getBigDecimal().equals((BigDecimal) f.get(this))) {
                    map.remove(field);
                }
                // no check for these types
            case IMAGE:
            case ISTRING_DELETE:
            case ISTRING:
            case IMAGE_DELETE:
            case IMAGE_DUMMY:
                break;
            default:
                throw new Exception("Base.check(): Type not implemented: " + u.getType().toString());
            }
        }
    }

    /**
     * Returns the class ID.
     */
    public abstract ClassID getCID();

    /**
     * Updates an entity.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param user
     *            user who updates the entity
     * @param map
     *            map containing the changes
     * @throws Exception
     */
    protected void update(DAOiface dao, User user, UpdateMap map) throws Exception {
        Class<?> c = this.getClass();

        for (Enum<?> field : map.getMap().keySet()) {
            Update u = map.get(field);
            Field f;
            try {
                f = c.getDeclaredField(field.name());
            } catch (java.lang.NoSuchFieldException e) {
                f = c.getSuperclass().getDeclaredField(field.name());
            }
            f.setAccessible(true);
            switch (u.getType()) {
            case STRING:
                f.set(this, u.getString());
                break;
            case DOUBLE:
                f.set(this, u.getDouble());
                break;
            case FLOAT:
                f.set(this, u.getFloat());
                break;
            case LONG:
                f.set(this, u.getLong());
                break;
            case INT:
                f.set(this, u.getInt());
                break;
            case LAZY_BINARY:
            case BINARY:
                f.set(this, u.getBinary());
                break;
            case BOOLEAN:
                f.set(this, u.getBoolean());
                break;
            case DATE:
                f.set(this, u.getDate());
                break;
            case ENUM:
                f.set(this, u.getEnum());
                break;
            case BIGDECIMAL:
                f.set(this, u.getBigDecimal());
                break;
            case ISTRING: {
                DBkey a = f.getAnnotation(DBkey.class);
                if (a == null || !a.value().equals(Istring.class)) {
                    throw new Exception("Base.update(): Field type mismatch for an IString");
                }
                ArrayList<IstringDummy> list = u.getIstringDummy();
                // dao.flush();
                for (IstringDummy i : list) {
                    Istring istr = null;
                    int userId = user != null ? user.getHistId() : 0;
                    if (f.getInt(this) == 0) {
                        istr = Istring.create(dao, domain, user, this.getCID(), i.getText(), i.getLanguage());
                    } else {
                        istr = Istring.saveOrUpdate(dao, domain, userId, f.getInt(this), i.getText(),
                                i.getLanguage(), i.getCountry(), this.getCID());
                    }
                    // only for CI make a reference form IString to the owner
                    if (this instanceof CI) {
                        istr.setCI(this.getHistId());
                    }
                    if (f.getInt(this) == 0) {
                        f.setInt(this, istr.getHistId());
                        dao.update(this);
                    }
                }
                break;
            }
            case ISTRING_DELETE: {
                DBkey a = f.getAnnotation(DBkey.class);
                if (a == null || !a.value().equals(Istring.class)) {
                    throw new Exception("Base.update(): Field type mismatch for an IString");
                }
                ArrayList<IstringDummy> list = u.getIstringDummy();
                for (IstringDummy i : list) {
                    if (i.getCountry() == null && i.getLanguage() == null) {
                        Istring.delete(dao, user, f.getInt(this));
                    } else if (i.getCountry() == null && i.getLanguage() != null) {
                        Istring.delete(dao, user, f.getInt(this), i.getLanguage());
                    } else if (i.getCountry() != null && i.getLanguage() != null) {
                        Istring.delete(dao, user, f.getInt(this), i.getLanguage(), i.getCountry());
                    }
                }
                break;
            }
            case IMAGE: {
                DBkey a = f.getAnnotation(DBkey.class);
                if (a == null || !a.value().equals(Image.class)) {
                    throw new Exception("Base.update(): Field type mismatch for an Image");
                }
                Image.update(dao, user, f.getInt(this), u.getUpdateMap());
                break;
            }
            case IMAGE_DUMMY: {
                DBkey a = f.getAnnotation(DBkey.class);
                if (a == null || !a.value().equals(Image.class)) {
                    throw new Exception("Base.update(): Field type mismatch for an Image");
                }
                int id = f.getInt(this);
                if (id == 0) {
                    ImageDummy idummy = u.getImageDummy();
                    if (this instanceof User) {
                        User uuser = (User) this;
                        Image i = Image.create(dao, null, user, "userImage_" + Base.getRandomLong(),
                                idummy.getData(), idummy.getMimeType(), idummy.getLicense());
                        uuser.setImage(i.getHistId());
                    } else if (this instanceof DBcategory) {
                        DBcategory cat = (DBcategory) this;
                        Image i = Image.create(dao, null, user, "catImage_" + Base.getRandomLong(),
                                idummy.getData(), idummy.getMimeType(), idummy.getLicense());
                        cat.setIcon(i.getHistId());
                    } else {
                        throw new Exception("Base.update(): ImageDummy for this relationship is't defined");
                    }
                } else {
                    Image.update(dao, user, id, u.getImageDummy().getImageUpdateMap());
                }
                break;
            }
            case IMAGE_DELETE: {
                DBkey a = f.getAnnotation(DBkey.class);
                if (a == null || !a.value().equals(Image.class)) {
                    throw new Exception("Base.update(): Field type mismatch for an Image");
                }
                Image.delete(dao, user, f.getInt(this));
                f.setInt(this, 0);
                break;
            }
            default:
                throw new Exception("Base.update(): Type not implemented!");
            }
        }
    }

    /**
     * Updates only embedded data types of an entity.
     * 
     * @param map
     *            map containing the changes
     * @param
     * @throws Exception
     */
    public void simpleUpdate(UpdateMap map, boolean strict) throws Exception {
        Class<?> c = this.getClass();

        for (Enum<?> field : map.getMap().keySet()) {
            Update u = map.get(field);
            Field f = c.getDeclaredField(field.name());
            f.setAccessible(true);
            switch (u.getType()) {
            case STRING:
                f.set(this, u.getString());
                break;
            case DOUBLE:
                f.set(this, u.getDouble());
                break;
            case FLOAT:
                f.set(this, u.getFloat());
                break;
            case LONG:
                f.set(this, u.getLong());
                break;
            case INT:
                f.set(this, u.getInt());
                break;
            case LAZY_BINARY:
            case BINARY:
                f.set(this, u.getBinary());
                break;
            case BOOLEAN:
                f.set(this, u.getBoolean());
                break;
            case DATE:
                f.set(this, u.getDate());
                break;
            case ENUM:
                f.set(this, u.getEnum());
                break;
            case BIGDECIMAL:
                f.set(this, u.getBigDecimal());
                break;
            default:
                if (strict) {
                    throw new Exception("Base.update(): Type not implemented!");
                }
            }
        }
    }

    /**
     * Counts the entries/table rows - DB related operation.
     * 
     * @param clazz
     *            class of the entity
     * @param status
     *            historisation status of the entity
     * @param where
     *            optional where clause (single statement)
     * @return entity count
     * @throws Exception
     */
    // TODO: Fix ugly where clause
    public static long countRow(DAOiface dao, Class<?> clazz, HistorizationIface.STATUS stat, String where)
            throws Exception {
        long size = 0;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            StringBuffer buf = new StringBuffer("SELECT count(c) FROM ");
            buf.append(clazz.getSimpleName());
            buf.append(" c");
            HashMap<String, Object> map = null;
            if (stat != null) {
                map = new HashMap<String, Object>();
                map.put("status", stat);
                buf.append(" where c.status = :status");
            }
            if (where != null) {
                buf.append(" AND c." + where);
            }

            Object s = dao.query(buf.toString(), map).get(0);
            if (s instanceof BigInteger) {
                BigInteger bi = (BigInteger) s;
                size = bi.longValue();
            } else {
                size = (Long) s;
            }
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return size;
    }

    /**
     * Field constraint - given text field of an entity must be unique for a
     * domain.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param update
     *            update data
     * @param domain
     *            {@code Domain} of the entity
     * @param dbID
     *            ID of the entity
     * @param fieldName
     *            text field name
     * @param value
     *            field value
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static void checkConstraintPerDomain(DAOiface dao, UpdateMap update, int domain, int dbID,
            Enum<?> fieldName, String value) throws Exception {
        List<Base> list = null;
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("domain", domain);
        map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
        String entry = fieldName.name();
        Class<?> clazz = fieldName.getDeclaringClass().getDeclaringClass();
        if (update == null) {
            map.put(entry, value);
            list = (List<Base>) dao.query("select i from " + clazz.getSimpleName()
                    + " i where i.domain = :domain and i." + entry + " = :" + entry + " and i.status = :status",
                    map);
        } else {
            Update m = update.get(fieldName);
            if (m != null) {
                map.put("histId", dbID);
                map.put("name", m.getString());
                list = (List<Base>) dao
                        .query("select i from " + clazz.getSimpleName() + " i where i.domain = :domain and i."
                                + entry + " = :" + entry + " and i.status = :status and i.histId <> :histId", map);
            }
        }
        if (list != null && list.size() > 0) {
            throw new Exception(
                    clazz.getSimpleName() + ".checkConstraints(): Property " + entry + " isn't unique:" + value);
        }
    }

    /**
     * Field constraint - given text field of an entity must be unique over all
     * domains.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param update
     *            update data
     * @param dbID
     *            DB ID of the entity
     * @param fieldName
     *            text field name
     * @param value
     *            field value
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static void checkConstraint(DAOiface dao, UpdateMap update, int dbID, Enum<?> fieldName, String value)
            throws Exception {
        List<Base> list = null;
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
        String entry = fieldName.name();
        Class<?> clazz = fieldName.getDeclaringClass().getDeclaringClass();
        if (update == null) {
            map.put(entry, value);
            list = (List<Base>) dao.query("select i from " + clazz.getSimpleName() + " i where i." + entry + " = :"
                    + entry + " and i.status = :status", map);
        } else {
            Update m = update.get(fieldName);
            if (m != null) {
                map.put("histId", dbID);
                map.put(entry, m.getString());
                list = (List<Base>) dao.query("select i from " + clazz.getSimpleName() + " i where i." + entry
                        + " = :" + entry + " and i.status = :status and i.histId <> :histId", map);
            }
        }
        if (list != null && list.size() > 0) {
            throw new Exception(
                    clazz.getSimpleName() + ".checkConstraints(): Property " + entry + " isn't unique:" + value);
        }
    }

    /**
     * Field constraint - given text field of a entity must be unique for a CI.
     * 
     * @param dao
     *            {@code DAOiface} (data access object)
     * @param update
     *            update data
     * @param domain
     *            {@code Domain} of the entity
     * @param ciID
     *            ID of the CI
     * @param dbID
     *            DB ID of the entity
     * @param fieldName
     *            text field name
     * @param value
     *            field value
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static void checkConstraintsPerCI(DAOiface dao, UpdateMap update, int domain, int ciID, int dbID,
            Enum<?> fieldName, String value) throws Exception {
        List<Base> list = null;
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("domain", domain);
        map.put("status", at.treedb.db.HistorizationIface.STATUS.ACTIVE);
        Class<?> clazz = fieldName.getDeclaringClass().getDeclaringClass();
        String entry = fieldName.name();
        if (update == null) {
            map.put(entry, value);
            map.put("ciId", ciID);
            list = (List<Base>) dao
                    .query("select i from " + clazz.getSimpleName() + " i where i.domain = :domain and i." + entry
                            + " = :" + entry + " and i.status = :status and i.ci = :ciId", map);
        } else {
            Update m = update.get(fieldName);
            if (m != null) {
                map.put("histId", dbID);
                map.put("ciId", ciID);
                map.put("name", m.getString());
                list = (List<Base>) dao.query("select i from " + clazz.getSimpleName()
                        + " i where i.domain = :domain and i." + entry + " = :" + entry
                        + " and i.ci =:ciId and i.status = :status and i.histId <> :histId", map);
            }
        }
        if (list != null && list.size() > 0) {
            throw new Exception(
                    clazz.getSimpleName() + ".checkConstraints(): Property " + entry + " isn't unique:" + value);
        }
    }

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    protected static int queryAndExecute(DAOiface dao, String query, HashMap<String, Object> map) throws Exception {
        int count = 0;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            count = dao.queryAndExecute(query, map);
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return count;
    }

    @SuppressWarnings("unchecked")
    protected static List<Base> query(DAOiface dao, String query, HashMap<String, Object> map) throws Exception {
        List<Base> base = null;
        boolean localDAO = false;
        if (dao == null) {
            dao = DAO.getDAO();
            localDAO = true;
        }
        try {
            if (localDAO) {
                dao.beginTransaction();
            }
            base = (List<Base>) dao.query(query, map);
            if (localDAO) {
                dao.endTransaction();
            }
        } catch (Exception e) {
            if (localDAO) {
                dao.rollback();
            }
            throw e;
        }
        return base;
    }

    public static long getRandomLong() {
        long value = random.nextLong();
        if (value < 0) {
            value = -value;
        }
        return value;
    }

}