org.openbravo.dal.service.OBDal.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.dal.service.OBDal.java

Source

/*
 *************************************************************************
 * The contents of this file are subject to the Openbravo  Public  License
 * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
 * Version 1.1  with a permitted attribution clause; you may not  use this
 * file except in compliance with the License. You  may  obtain  a copy of
 * the License at http://www.openbravo.com/legal/license.html 
 * Software distributed under the License  is  distributed  on  an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific  language  governing  rights  and  limitations
 * under the License. 
 * The Original Code is Openbravo ERP. 
 * The Initial Developer of the Original Code is Openbravo SLU 
 * All portions are Copyright (C) 2008-2011 Openbravo SLU 
 * All Rights Reserved. 
 * Contributor(s):  ______________________________________.
 ************************************************************************
 */

package org.openbravo.dal.service;

import java.io.Serializable;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.impl.SessionFactoryImpl;
import org.hibernate.jdbc.BorrowedConnectionProxy;
import org.hibernate.stat.SessionStatistics;
import org.openbravo.base.model.Entity;
import org.openbravo.base.model.ModelProvider;
import org.openbravo.base.model.Property;
import org.openbravo.base.model.UniqueConstraint;
import org.openbravo.base.provider.OBProvider;
import org.openbravo.base.provider.OBSingleton;
import org.openbravo.base.session.SessionFactoryController;
import org.openbravo.base.structure.BaseOBObject;
import org.openbravo.base.structure.ClientEnabled;
import org.openbravo.base.structure.OrganizationEnabled;
import org.openbravo.dal.core.DalSessionFactory;
import org.openbravo.dal.core.DalUtil;
import org.openbravo.dal.core.OBContext;
import org.openbravo.dal.core.SessionHandler;
import org.openbravo.dal.security.SecurityChecker;
import org.openbravo.model.ad.system.Client;
import org.openbravo.model.common.enterprise.Organization;

/**
 * The OBDal class offers the main external access to the Data Access Layer. The variety of data
 * access methods are provided such as save, get, query, remove, etc.
 * 
 * @see OBCriteria
 * @see OBQuery
 * 
 * @author mtaal
 */
// TODO: add methods to return a sorted list based on the identifier of an
// object
// TODO: re-check singleton pattern when a new factory/dependency injection
// approach is implemented.
public class OBDal implements OBSingleton {
    private static final Logger log = Logger.getLogger(OBDal.class);

    private static OBDal instance;

    /**
     * @return the singleton instance of the OBDal service
     */
    public static OBDal getInstance() {
        if (instance == null) {
            instance = OBProvider.getInstance().get(OBDal.class);
        }
        return instance;
    }

    /**
     * After calling this method all collections and queries will only return objects which are
     * active. Note that this overrides the active filtering setting on
     * {@link OBQuery#setFilterOnActive(boolean)} and {@link OBCriteria#setFilterOnActive(boolean)}.
     * 
     * @see #disableActiveFilter()
     */
    public void enableActiveFilter() {
        SessionHandler.getInstance().getSession().enableFilter("activeFilter").setParameter("activeParam", "Y");
    }

    /**
     * Register a sql function in the session factory, after this call it can be used by queries.
     */
    public void registerSQLFunction(String name, SQLFunction function) {
        final DalSessionFactory dalSessionFactory = (DalSessionFactory) SessionFactoryController.getInstance()
                .getSessionFactory();

        final Dialect dialect = ((SessionFactoryImpl) dalSessionFactory.getDelegateSessionFactory()).getDialect();
        dialect.getFunctions().put(name, function);
    }

    /**
     * After calling this method the active filter is disabled. Note that then the settings in
     * {@link OBQuery#setFilterOnActive(boolean)} and {@link OBCriteria#setFilterOnActive(boolean)}
     * will apply.
     * 
     * @see #enableActiveFilter()
     */
    public void disableActiveFilter() {
        SessionHandler.getInstance().getSession().disableFilter("activeFilter");
    }

    public boolean isActiveFilterEnabled() {
        return SessionHandler.getInstance().getSession().getEnabledFilter("activeFilter") != null;
    }

    /**
     * Returns the connection used by the hibernate session.
     * 
     * Note: flushes the hibernate session before returning the connection.
     * 
     * @return the current database connection
     * @see #flush()
     */
    public Connection getConnection() {
        return getConnection(true);
    }

    /**
     * Returns the connection used by the hibernate session.
     * 
     * @param doFlush
     *          if true then the current actions are first flushed.
     * 
     * @return the current database connection
     * @see #flush()
     */
    public Connection getConnection(boolean doFlush) {
        if (doFlush) {
            // before returning a connection flush all other hibernate actions
            // to the database.
            flush();
        }

        // NOTE: workaround for this issue:
        // http://opensource.atlassian.com/projects/hibernate/browse/HHH-3529
        final ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(BorrowedConnectionProxy.class.getClassLoader());
            final Connection connection = ((SessionImplementor) SessionHandler.getInstance().getSession())
                    .connection();
            return connection;
        } finally {
            Thread.currentThread().setContextClassLoader(currentLoader);
        }
    }

    /**
     * @return the current hibernate session
     */
    public Session getSession() {
        return SessionHandler.getInstance().getSession();
    }

    /**
     * Commits the transaction and closes session.
     */
    public void commitAndClose() {
        if (SessionHandler.isSessionHandlerPresent()) {
            SessionHandler.getInstance().commitAndClose();
        }
    }

    /**
     * Rolls back the transaction and closes the session.
     */
    public void rollbackAndClose() {
        if (SessionHandler.isSessionHandlerPresent()) {
            SessionHandler.getInstance().rollback();
        }
    }

    /**
     * Utility method to log all entities loaded into the current hibernate session. Useful to debug
     * slow flush() calls.
     */
    private void dumpSessionEntities() {
        SessionStatistics sessStat = SessionHandler.getInstance().getSession().getStatistics();
        log.debug("Dumping all entities in session");
        for (Object o : sessStat.getEntityKeys()) {
            log.debug(o);
        }
    }

    /**
     * Flushes the current state to the database.
     */
    public void flush() {
        if (SessionHandler.isSessionHandlerPresent()) {
            long s1 = System.currentTimeMillis();
            SessionHandler.getInstance().getSession().flush();
            if (log.isDebugEnabled()) {
                long s2 = System.currentTimeMillis();
                SessionStatistics sessStat = SessionHandler.getInstance().getSession().getStatistics();
                dumpSessionEntities();
                log.debug("Flush of " + sessStat.getEntityCount() + " entities and " + sessStat.getCollectionCount()
                        + " collections took: " + (s2 - s1), new Throwable());
            }
        }
    }

    /**
     * Sets the client and organization of the object (if not set) and persists the object in the
     * database.
     * 
     * @param obj
     *          the object to persist
     */
    public void save(Object obj) {

        // prevent saving of db view objects, this can happen for example if someone accidentally
        // exported views in xml and then imports this xml again
        if (obj instanceof BaseOBObject && ((BaseOBObject) obj).getEntity().isView()) {
            log.warn("Trying to save an object which is a db-view, ignoring save operation, entity: "
                    + ((BaseOBObject) obj).getEntity().getName());
            return;
        }

        // set client organization has to be done here before checking write
        // access
        // not the most nice to do
        // TODO: add checking if setClientOrganization is really necessary
        // TODO: log using entityName
        log.debug("Saving object " + obj.getClass().getName());
        setClientOrganization(obj);
        if (!OBContext.getOBContext().isInAdministratorMode()) {
            if (obj instanceof BaseOBObject) {
                OBContext.getOBContext().getEntityAccessChecker().checkWritable(((BaseOBObject) obj).getEntity());
            }
            SecurityChecker.getInstance().checkWriteAccess(obj);
        }
        SessionHandler.getInstance().save(obj);
    }

    /**
     * Removes the object from the database.
     * 
     * @param obj
     *          the object to be removed
     */
    public void remove(Object obj) {

        // prevent removing of db view objects, this can happen for example if someone accidentally
        // exported views in xml and posts this xml using a webservice
        if (obj instanceof BaseOBObject && ((BaseOBObject) obj).getEntity().isView()) {
            log.warn("Trying to remove an object which is a db-view, ignoring remove operation, entity: "
                    + ((BaseOBObject) obj).getEntity().getName());
            return;
        }

        // TODO: add checking if setClientOrganization is really necessary
        // TODO: log using entityName
        log.debug("Removing object " + obj.getClass().getName());
        SecurityChecker.getInstance().checkDeleteAllowed(obj);
        SessionHandler.getInstance().delete(obj);
    }

    /**
     * Refresh the given object from the database. Also initialized lists inside the object will be
     * refreshed.
     * 
     * @param obj
     *          the object to refresh
     * @see Session#refresh(Object)
     */
    public void refresh(Object obj) {
        SessionHandler.getInstance().getSession().refresh(obj);
    }

    /**
     * Retrieves an object from the database using the class and id.
     * 
     * @param clazz
     *          the type of object to search for
     * @param id
     *          the id of the object
     * @return the object, or null if none found
     */
    public <T extends Object> T get(Class<T> clazz, Object id) {
        checkReadAccess(clazz);
        return SessionHandler.getInstance().find(clazz, id);
    }

    /**
     * Returns true if an object (identified by the entityName and id) exists, false otherwise.
     * 
     * @param entityName
     *          the name of the entity
     * @param id
     *          the id used to find the instance
     * @return true if exists, false otherwise
     */
    public boolean exists(String entityName, Object id) {
        return null != SessionHandler.getInstance().find(entityName, id);
    }

    /**
     * Retrieves an object from the database using the entity name and id.
     * 
     * @param entityName
     *          the type of object to search for
     * @param id
     *          the id of the object
     * @return the object, or null if none found
     */
    public BaseOBObject get(String entityName, Object id) {
        checkReadAccess(entityName);
        return SessionHandler.getInstance().find(entityName, id);
    }

    /**
     * Will return a non-loaded hibernate proxy if the object was not already loaded by hibernate.
     * 
     * NOTE/BEWARE: this method will not check if the object actually exists in the database. This
     * will detected when persisting a referencing object or when this proxy gets initialized!
     * 
     * This method differs from other get methods in this class, these methods will always eagerly
     * load the object and thereby also immediately check the existence of these referenced objects.
     * 
     * @param entityName
     *          the type of object to search for
     * @param id
     *          the id of the object
     * @return the object, or null if none found
     */
    public BaseOBObject getProxy(String entityName, Object id) {
        return (BaseOBObject) ((SessionImplementor) getSession()).internalLoad(entityName, (Serializable) id, false,
                false);
    }

    /**
     * Create a OBQuery object using a class and a specific where and order by clause.
     * 
     * @param fromClz
     *          the class to create the query for
     * @param whereOrderByClause
     *          the HQL where and orderby clause
     * @return the query object
     */
    public <T extends BaseOBObject> OBQuery<T> createQuery(Class<T> fromClz, String whereOrderByClause) {
        return createQuery(fromClz, whereOrderByClause, new ArrayList<Object>());
    }

    /**
     * Create a OBQuery object using a class and a specific where and order by clause and a set of
     * parameters which are used in the query.
     * 
     * @param fromClz
     *          the class to create the query for
     * @param whereOrderByClause
     *          the HQL where and orderby clause
     * @param parameters
     *          the parameters to use in the query
     * @return the query object
     */
    public <T extends BaseOBObject> OBQuery<T> createQuery(Class<T> fromClz, String whereOrderByClause,
            List<Object> parameters) {
        checkReadAccess(fromClz);
        final OBQuery<T> obQuery = new OBQuery<T>();
        obQuery.setWhereAndOrderBy(whereOrderByClause);
        obQuery.setEntity(ModelProvider.getInstance().getEntity(fromClz));
        obQuery.setParameters(parameters);
        return obQuery;
    }

    /**
     * Create a OBQuery object using an entity name and a specific where and order by clause.
     * 
     * @param entityName
     *          the type to create the query for
     * @param whereOrderByClause
     *          the HQL where and orderby clause
     * @return the new query object
     */
    public OBQuery<BaseOBObject> createQuery(String entityName, String whereOrderByClause) {
        return createQuery(entityName, whereOrderByClause, new ArrayList<Object>());
    }

    /**
     * Create a OBQuery object using an entity name and a specific where and order by clause and a set
     * of parameters which are used in the query.
     * 
     * @param entityName
     *          the type to create the query for
     * @param whereOrderByClause
     *          the HQL where and orderby clause
     * @param parameters
     *          the parameters to use in the query
     * @return a new instance of {@link OBQuery}.
     */
    public OBQuery<BaseOBObject> createQuery(String entityName, String whereOrderByClause,
            List<Object> parameters) {
        checkReadAccess(entityName);
        final OBQuery<BaseOBObject> obQuery = new OBQuery<BaseOBObject>();
        obQuery.setWhereAndOrderBy(whereOrderByClause);
        obQuery.setEntity(ModelProvider.getInstance().getEntity(entityName));
        obQuery.setParameters(parameters);
        return obQuery;
    }

    /**
     * Creates an OBCriteria object for the specified class.
     * 
     * @param clz
     *          the class used to create the OBCriteria
     * @return a new OBCriteria object
     */
    public <T extends BaseOBObject> OBCriteria<T> createCriteria(Class<T> clz) {
        checkReadAccess(clz);
        final Entity entity = ModelProvider.getInstance().getEntity(clz);
        final OBCriteria<T> obCriteria = new OBCriteria<T>(entity.getName());
        obCriteria.setEntity(entity);
        return obCriteria;
    }

    /**
     * Creates an OBCriteria object for the specified class.
     * 
     * @param clz
     *          the class used to create the OBCriteria
     * @param alias
     *          an alias that can be used to refer to the specified object
     * @return a new OBCriteria object
     */
    public <T extends BaseOBObject> OBCriteria<T> createCriteria(Class<T> clz, String alias) {
        checkReadAccess(clz);
        final Entity entity = ModelProvider.getInstance().getEntity(clz);
        final OBCriteria<T> obCriteria = new OBCriteria<T>(entity.getName(), alias);
        obCriteria.setEntity(entity);
        return obCriteria;
    }

    /**
     * Creates an OBCriteria object for the specified entity.
     * 
     * @param entityName
     *          the type used to create the OBCriteria
     * @return a new OBCriteria object
     */
    public <T extends BaseOBObject> OBCriteria<T> createCriteria(String entityName) {
        checkReadAccess(entityName);
        final OBCriteria<T> obCriteria = new OBCriteria<T>(entityName);
        obCriteria.setEntity(ModelProvider.getInstance().getEntity(entityName));
        return obCriteria;
    }

    /**
     * Creates an OBCriteria object for the specified entity.
     * 
     * @param entityName
     *          the type used to create the OBCriteria
     * @param alias
     *          an alias that can be used to refer to the specified object
     * @return a new OBCriteria object
     */
    public <T extends BaseOBObject> OBCriteria<T> createCriteria(String entityName, String alias) {
        checkReadAccess(entityName);
        final OBCriteria<T> obCriteria = new OBCriteria<T>(entityName, alias);
        obCriteria.setEntity(ModelProvider.getInstance().getEntity(entityName));
        return obCriteria;
    }

    /**
     * Retrieves a list of baseOBObjects using the unique-constraints defined for the entity. The
     * passed BaseOBObject and the unique-constraints are used to construct a query searching for
     * matching objects in the database.
     * <p/>
     * Note that multiple unique constraints are used, so therefore the result can be more than one
     * object.
     * 
     * @param obObject
     *          this property values of this obObject is used to find other objects in the database
     *          with the same property values for the unique constraint properties
     * @return a list of objects which match the passed obObject on the unique constraint properties
     * @see Entity#getUniqueConstraints()
     */
    public List<BaseOBObject> findUniqueConstrainedObjects(BaseOBObject obObject) {
        final Entity entity = obObject.getEntity();
        final List<BaseOBObject> result = new ArrayList<BaseOBObject>();
        final Object id = obObject.getId();
        for (final UniqueConstraint uc : entity.getUniqueConstraints()) {
            final OBCriteria<BaseOBObject> criteria = createCriteria(entity.getName());
            if (id != null) {
                criteria.add(Restrictions.ne("id", id));
            }
            for (final Property p : uc.getProperties()) {
                final Object value = obObject.getValue(p.getName());
                criteria.add(Restrictions.eq(p.getName(), value));
            }
            final List<BaseOBObject> queryResult = criteria.list();
            // this is not fast, but the list should be small normally
            // if performance becomes a problem then a hashset should
            // be used.
            for (final BaseOBObject queriedObject : queryResult) {
                if (!result.contains(queriedObject)) {
                    result.add(queriedObject);
                }
            }
        }

        return result;
    }

    // TODO: this is maybe not the best location for this functionality??
    protected void setClientOrganization(Object o) {
        final OBContext obContext = OBContext.getOBContext();
        if (o instanceof ClientEnabled) {
            final ClientEnabled ce = (ClientEnabled) o;
            // reread the client
            if (ce.getClient() == null) {
                final Client client = SessionHandler.getInstance().find(Client.class,
                        obContext.getCurrentClient().getId());
                ce.setClient(client);
            }
        }
        if (o instanceof OrganizationEnabled) {
            final OrganizationEnabled oe = (OrganizationEnabled) o;
            // reread the client and organization
            if (oe.getOrganization() == null) {
                final Organization org = SessionHandler.getInstance().find(Organization.class,
                        obContext.getCurrentOrganization().getId());
                oe.setOrganization(org);
            }
        }
    }

    /**
     * Returns an in-clause HQL clause denoting the organizations which are allowed to be read by the
     * current user. The in-clause can be directly used in a HQL. The return string will be for
     * example: in ('1000000', '1000001')
     * 
     * @return an in-clause which can be directly used inside of a HQL clause
     * @see OBContext#getReadableOrganizations()
     */
    public String getReadableOrganizationsInClause() {
        return createInClause(OBContext.getOBContext().getReadableOrganizations());
    }

    /**
     * Returns an in-clause HQL clause denoting the clients which are allowed to be read by the
     * current user. The in-clause can be directly used in a HQL. The return string will be for
     * example: in ('1000000', '1000001')
     * 
     * @return an in-clause which can be directly used inside of a HQL clause
     * @see OBContext#getReadableClients()
     */
    public String getReadableClientsInClause() {
        return createInClause(OBContext.getOBContext().getReadableClients());
    }

    private String createInClause(String[] values) {
        if (values.length == 0) {
            return " in ('') ";
        }
        final StringBuilder sb = new StringBuilder();
        for (final String v : values) {
            if (sb.length() > 0) {
                sb.append(", ");
            }
            sb.append("'" + v + "'");
        }
        return " in (" + sb.toString() + ")";
    }

    private void checkReadAccess(Class<?> clz) {
        checkReadAccess(DalUtil.getEntityName(clz));
    }

    private void checkReadAccess(String entityName) {
        // allow read access to those, otherwise it is really
        // difficult to use querying on these very generic values
        if (entityName.equals(Client.ENTITY_NAME) || entityName.equals(Organization.ENTITY_NAME)) {
            return;
        }
        if (OBContext.getOBContext().isInAdministratorMode()) {
            return;
        }
        final Entity e = ModelProvider.getInstance().getEntity(entityName);
        OBContext.getOBContext().getEntityAccessChecker().checkReadable(e);
    }
}