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;
      }
    }
  }
  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;
  }
  @Override
  public Iterable<T> findAll() {
    QueryModel queryModel = new QueryModel();

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

    return findUsingQueryModel(queryModel);
  }
  @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);
  }
  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;
  }
  @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 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;
  }
  // 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;
  }