org.iternine.jeppetto.dao.hibernate.HibernateQueryModelDAO.java Source code

Java tutorial

Introduction

Here is the source code for org.iternine.jeppetto.dao.hibernate.HibernateQueryModelDAO.java

Source

/*
 * Copyright (c) 2011 Jeppetto and Jonathan Thompson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.iternine.jeppetto.dao.hibernate;

import org.iternine.jeppetto.dao.AccessControlContext;
import org.iternine.jeppetto.dao.AccessControlContextProvider;
import org.iternine.jeppetto.dao.AccessControllable;
import org.iternine.jeppetto.dao.Condition;
import org.iternine.jeppetto.dao.ConditionType;
import org.iternine.jeppetto.dao.NoSuchItemException;
import org.iternine.jeppetto.dao.Projection;
import org.iternine.jeppetto.dao.ProjectionType;
import org.iternine.jeppetto.dao.QueryModel;
import org.iternine.jeppetto.dao.QueryModelDAO;
import org.iternine.jeppetto.dao.Sort;
import org.iternine.jeppetto.dao.SortDirection;
import org.iternine.jeppetto.dao.annotation.AccessControl;
import org.iternine.jeppetto.dao.annotation.AccessControlRule;
import org.iternine.jeppetto.dao.annotation.AccessControlType;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.TypedValue;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.loader.criteria.CriteriaQueryTranslator;
import org.hibernate.type.StringType;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * An implementation of the QueryModelDAO interface that supports Hibernate.
 *
 * @param <T> persistent class.
 * @param <ID> ID type for the persistent class.
 */
public class HibernateQueryModelDAO<T, ID extends Serializable>
        implements QueryModelDAO<T, ID>, AccessControllable<ID> {

    //-------------------------------------------------------------
    // Constants
    //-------------------------------------------------------------

    private static final StringType STRING_TYPE = new StringType();

    //-------------------------------------------------------------
    // Variables - Private
    //-------------------------------------------------------------

    private Class<T> persistentClass;
    private SessionFactory sessionFactory;
    private AccessControlEntryHelper accessControlEntryHelper;
    private AccessControlContextProvider accessControlContextProvider;
    private String idField = "id"; // TODO: Allow for configuration...

    //-------------------------------------------------------------
    // Constructors
    //-------------------------------------------------------------

    public HibernateQueryModelDAO(Class<T> persistentClass, Map<String, Object> daoProperties) {
        this(persistentClass, daoProperties, null);
    }

    public HibernateQueryModelDAO(Class<T> persistentClass, Map<String, Object> daoProperties,
            AccessControlContextProvider accessControlContextProvider) {
        this.persistentClass = persistentClass;
        this.sessionFactory = (SessionFactory) daoProperties.get("sessionFactory");
        this.accessControlEntryHelper = (AccessControlEntryHelper) daoProperties.get("accessControlEntryHelper");
        this.accessControlContextProvider = accessControlContextProvider;
    }

    //-------------------------------------------------------------
    // Implementation - GenericDAO
    //-------------------------------------------------------------

    @Override
    public T findById(ID id) throws NoSuchItemException {
        QueryModel queryModel = new QueryModel();
        queryModel.addCondition(buildIdCondition(id));

        if (accessControlContextProvider != null) {
            queryModel.setAccessControlContext(accessControlContextProvider.getCurrent());
        }

        return findUniqueUsingQueryModel(queryModel);
    }

    @Override
    public Iterable<T> findAll() {
        QueryModel queryModel = new QueryModel();

        if (accessControlContextProvider != null) {
            queryModel.setAccessControlContext(accessControlContextProvider.getCurrent());
        }

        return findUsingQueryModel(queryModel);
    }

    @Override
    public void save(T entity) {
        try {
            getCurrentSession().saveOrUpdate(entity);
        } catch (HibernateException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void delete(T entity) {
        try {
            getCurrentSession().delete(entity);
        } catch (HibernateException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteById(ID id) {
        try {
            getCurrentSession().delete(findById(id));
        } catch (NoSuchItemException ignore) {
            // If it doesn't exist, no need to delete.
        } catch (HibernateException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void flush() {
        getCurrentSession().flush();
    }

    //-------------------------------------------------------------
    // Implementation - QueryModelDAO
    //-------------------------------------------------------------

    @Override
    public T findUniqueUsingQueryModel(QueryModel queryModel) throws NoSuchItemException {
        T result;

        if (accessControlContextProvider == null
                || roleAllowsAccess(queryModel.getAccessControlContext().getRole())) {
            // noinspection unchecked
            result = (T) buildCriteria(queryModel).uniqueResult();
        } else {
            // noinspection unchecked
            result = (T) createEntryBasedQuery(queryModel).uniqueResult();
        }

        if (result == null) {
            throw new NoSuchItemException(persistentClass.getSimpleName(), queryModel.toString());
        }

        return result;
    }

    @Override
    public Iterable<T> findUsingQueryModel(QueryModel queryModel) {
        if (accessControlContextProvider == null
                || roleAllowsAccess(queryModel.getAccessControlContext().getRole())) {
            Criteria criteria = buildCriteria(queryModel);

            if (queryModel.getSorts() != null) {
                for (Sort sort : queryModel.getSorts()) {
                    criteria.addOrder(
                            sort.getSortDirection() == SortDirection.Ascending ? Order.asc(sort.getField())
                                    : Order.desc(sort.getField()));
                }
            }

            if (queryModel.getMaxResults() > 0) {
                criteria.setMaxResults(queryModel.getMaxResults());
            }

            if (queryModel.getFirstResult() > 0) {
                criteria.setFirstResult(queryModel.getFirstResult());
            }

            //noinspection unchecked
            return criteria.list();
        } else {
            //noinspection unchecked
            return createEntryBasedQuery(queryModel).list();
        }
    }

    @Override
    public Object projectUsingQueryModel(QueryModel queryModel) {
        return buildCriteria(queryModel).uniqueResult();
    }

    @Override
    public Condition buildCondition(String conditionField, ConditionType conditionType, Iterator argsIterator) {
        Condition condition = new Condition();

        condition.setField(conditionField);

        switch (conditionType) {
        case Between:
            condition.setConstraint(Restrictions.between(conditionField, argsIterator.next(), argsIterator.next()));
            break;

        case Equal:
            condition.setConstraint(Restrictions.eq(conditionField, argsIterator.next()));
            break;

        case GreaterThan:
            condition.setConstraint(Restrictions.gt(conditionField, argsIterator.next()));
            break;

        case GreaterThanEqual:
            condition.setConstraint(Restrictions.ge(conditionField, argsIterator.next()));
            break;

        case IsNotNull:
            condition.setConstraint(Restrictions.isNotNull(conditionField));
            break;

        case IsNull:
            condition.setConstraint(Restrictions.isNull(conditionField));
            break;

        case LessThan:
            condition.setConstraint(Restrictions.lt(conditionField, argsIterator.next()));
            break;

        case LessThanEqual:
            condition.setConstraint(Restrictions.le(conditionField, argsIterator.next()));
            break;

        case NotEqual:
            condition.setConstraint(Restrictions.ne(conditionField, argsIterator.next()));
            break;

        case NotWithin:
            condition.setConstraint(
                    Restrictions.not(Restrictions.in(conditionField, (Collection) argsIterator.next())));
            break;

        case Within:
            condition.setConstraint(Restrictions.in(conditionField, (Collection) argsIterator.next()));
            break;
        }

        return condition;
    }

    @Override
    public Projection buildProjection(String projectionField, ProjectionType projectionType,
            Iterator argsIterator) {
        Projection projection = new Projection();

        projection.setField(projectionField);

        switch (projectionType) {
        case RowCount:
            projection.setDetails(Projections.rowCount());
            break;

        case Count:
            projection.setDetails(Projections.count(projectionField));
            break;

        case CountDistinct:
            projection.setDetails(Projections.countDistinct(projectionField));
            break;

        case Maximum:
            projection.setDetails(Projections.max(projectionField));
            break;

        case Minimum:
            projection.setDetails(Projections.min(projectionField));
            break;

        case Average:
            projection.setDetails(Projections.avg(projectionField));
            break;

        case Sum:
            projection.setDetails(Projections.sum(projectionField));
            break;

        default:
            throw new RuntimeException("Unexpected projection type: " + projectionType);
        }

        return projection;
    }

    //-------------------------------------------------------------
    // Implementation - AccessControllable
    //-------------------------------------------------------------

    @Override
    public AccessControlContextProvider getAccessControlContextProvider() {
        return accessControlContextProvider;
    }

    @Override
    public void grantAccess(ID id, String accessId) {
        accessControlEntryHelper.createEntry(persistentClass, id, accessId);
    }

    @Override
    public void revokeAccess(ID id, String accessId) {
        accessControlEntryHelper.deleteEntry(persistentClass, id, accessId);
    }

    @Override
    public List<String> getAccessIds(ID id) {
        return accessControlEntryHelper.getEntries(persistentClass, id);
    }

    //-------------------------------------------------------------
    // Methods - Protected
    //-------------------------------------------------------------

    protected Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }

    protected Condition buildIdCondition(ID id) {
        Condition condition = new Condition();

        condition.setField(idField);
        condition.setConstraint(Restrictions.eq(idField, id));

        return condition;
    }

    //-------------------------------------------------------------
    // Methods - Private
    //-------------------------------------------------------------

    private boolean roleAllowsAccess(String role) {
        AccessControl accessControl;

        if (role == null || role.isEmpty()) {
            return false;
        }

        if ((accessControl = getAccessControlAnnotation()) == null) {
            return false;
        }

        for (AccessControlRule accessControlRule : accessControl.rules()) {
            if (accessControlRule.type() == AccessControlType.Role && accessControlRule.value().equals(role)) {
                return true;
            }
        }

        return false;
    }

    private AccessControl getAccessControlAnnotation() {
        Class classToExamine = persistentClass;

        while (classToExamine != null) {
            // noinspection unchecked
            AccessControl accessControl = (AccessControl) classToExamine.getAnnotation(AccessControl.class);

            if (accessControl != null) {
                return accessControl;
            }

            classToExamine = classToExamine.getSuperclass();
        }

        return null;
    }

    private Criteria buildCriteria(QueryModel queryModel) {
        Criteria criteria = getCurrentSession().createCriteria(persistentClass);

        if (queryModel.getConditions() != null) {
            for (Condition condition : queryModel.getConditions()) {
                criteria.add((Criterion) condition.getConstraint());
            }
        }

        for (Map.Entry<String, List<Condition>> associationCriteriaEntry : queryModel.getAssociationConditions()
                .entrySet()) {
            Criteria associationCriteria = criteria.createCriteria(associationCriteriaEntry.getKey());

            criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

            for (Condition condition : associationCriteriaEntry.getValue()) {
                associationCriteria.add((Criterion) condition.getConstraint());
            }
        }

        if (queryModel.getProjection() != null) {
            ProjectionList projectionList = Projections.projectionList();

            projectionList.add((org.hibernate.criterion.Projection) queryModel.getProjection().getDetails());

            criteria.setProjection(projectionList);
        }

        return criteria;
    }

    // TODO: Add projection, maxResults, firstResult support
    private Query createEntryBasedQuery(QueryModel queryModel) {
        Criteria criteria = getCurrentSession().createCriteria(persistentClass);

        for (String associationPath : queryModel.getAssociationConditions().keySet()) {
            criteria.createCriteria(associationPath);
        }

        CriteriaQueryTranslator criteriaQueryTranslator = new CriteriaQueryTranslator(
                (SessionFactoryImplementor) sessionFactory, (CriteriaImpl) criteria, persistentClass.getName(),
                CriteriaQueryTranslator.ROOT_SQL_ALIAS);

        StringBuilder queryStringBuilder = new StringBuilder();

        buildSelectClause(queryStringBuilder, criteriaQueryTranslator,
                queryModel.getAssociationConditions().keySet());

        List<TypedValue> parameters = buildWhereClause(queryStringBuilder, queryModel, criteria,
                criteriaQueryTranslator);

        if ((queryModel.getAssociationConditions() == null || queryModel.getAssociationConditions().isEmpty())
                && (queryModel.getSorts() == null || queryModel.getSorts().isEmpty())) {
            buildDefaultOrderClause(queryStringBuilder);
        } else {
            // can't use the default ordering by "ace.id" because of "select distinct..." syntax
            buildOrderClause(queryStringBuilder, queryModel, criteria, criteriaQueryTranslator);
        }

        Query query = getCurrentSession().createQuery(queryStringBuilder.toString());

        setParameters(parameters, query, queryModel.getAccessControlContext());

        return query;
    }

    private void buildSelectClause(StringBuilder queryStringBuilder,
            CriteriaQueryTranslator criteriaQueryTranslator, Set<String> associationPaths) {
        // "distinct" is needed only for association criteria, but it prevents specifying ordering by "ace.id"
        if (associationPaths.isEmpty()) {
            queryStringBuilder.append("select ");
        } else {
            queryStringBuilder.append("select distinct ");
        }
        queryStringBuilder.append(criteriaQueryTranslator.getRootSQLALias());
        queryStringBuilder.append(" from AccessControlEntry ace, ");
        queryStringBuilder.append(persistentClass.getSimpleName());
        queryStringBuilder.append(' ');
        queryStringBuilder.append(criteriaQueryTranslator.getRootSQLALias());

        for (String associationPath : associationPaths) {
            queryStringBuilder.append(" join ");
            queryStringBuilder.append(criteriaQueryTranslator.getRootSQLALias());
            queryStringBuilder.append('.');
            queryStringBuilder.append(associationPath);
            queryStringBuilder.append(" as ");
            queryStringBuilder.append(
                    criteriaQueryTranslator.getSQLAlias(criteriaQueryTranslator.getCriteria(associationPath)));
        }
    }

    private List<TypedValue> buildWhereClause(StringBuilder queryStringBuilder, QueryModel queryModel,
            Criteria criteria, CriteriaQueryTranslator criteriaQueryTranslator) {
        List<TypedValue> parameters = new ArrayList<TypedValue>();

        queryStringBuilder.append(" where ");

        if (queryModel.getConditions() != null) {
            for (Condition condition : queryModel.getConditions()) {
                Criterion criterion = (Criterion) condition.getConstraint();

                queryStringBuilder.append(criterion.toSqlString(criteria, criteriaQueryTranslator));
                queryStringBuilder.append(" and ");

                parameters.addAll(Arrays.asList(criterion.getTypedValues(criteria, criteriaQueryTranslator)));
            }
        }

        if (queryModel.getAssociationConditions() != null) {
            for (Map.Entry<String, List<Condition>> associationCriteriaEntry : queryModel.getAssociationConditions()
                    .entrySet()) {
                CriteriaImpl.Subcriteria associationCriteria = (CriteriaImpl.Subcriteria) criteriaQueryTranslator
                        .getCriteria(associationCriteriaEntry.getKey());

                for (Condition condition : associationCriteriaEntry.getValue()) {
                    Criterion criterion = (Criterion) condition.getConstraint();

                    queryStringBuilder.append(criterion.toSqlString(associationCriteria, criteriaQueryTranslator));
                    queryStringBuilder.append(" and ");

                    parameters.addAll(
                            Arrays.asList(criterion.getTypedValues(associationCriteria, criteriaQueryTranslator)));
                }
            }
        }

        queryStringBuilder.append(" ace.objectType = '");
        queryStringBuilder.append(persistentClass.getSimpleName());
        queryStringBuilder.append("' and ace.objectId = ");
        queryStringBuilder.append(criteriaQueryTranslator.getRootSQLALias());
        queryStringBuilder.append('.');
        queryStringBuilder.append(idField);
        queryStringBuilder.append(" and ace.accessibleBy = ? ");

        return parameters;
    }

    private void buildOrderClause(StringBuilder queryStringBuilder, QueryModel queryModel, Criteria criteria,
            CriteriaQueryTranslator criteriaQueryTranslator) {
        boolean firstOrderItem = true;

        if (queryModel.getSorts() != null) {
            for (Sort sort : queryModel.getSorts()) {
                if (firstOrderItem) {
                    queryStringBuilder.append(" order by ");
                } else {
                    queryStringBuilder.append(',');
                }

                Order order = sort.getSortDirection() == SortDirection.Ascending ? Order.asc(sort.getField())
                        : Order.desc(sort.getField());

                queryStringBuilder.append(order.toSqlString(criteria, criteriaQueryTranslator));

                firstOrderItem = false;
            }
        }
    }

    /**
     * Default ordering by ACE.id field (integer) to ensure older-to-newer entries
     *
     * @param queryStringBuilder main query string tbeing built
     */
    private void buildDefaultOrderClause(StringBuilder queryStringBuilder) {
        queryStringBuilder.append(" order by ace.id asc");
    }

    private void setParameters(List<TypedValue> parameters, Query query,
            AccessControlContext accessControlContext) {
        int position = 0;

        for (TypedValue parameter : parameters) {
            query.setParameter(position++, parameter.getValue(), parameter.getType());
        }

        query.setParameter(position, accessControlContext.getAccessId(), STRING_TYPE);
    }
}