com.reignite.query.StructuredQuery.java Source code

Java tutorial

Introduction

Here is the source code for com.reignite.query.StructuredQuery.java

Source

package com.reignite.query;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.metadata.ClassMetadata;

import com.reignite.parser.Join;
import com.reignite.parser.JoinType;
import com.reignite.parser.QueryType;
import com.reignite.parser.exception.ParserException;

/*
Copyright (c) 2014 Surrey Hughes
    
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
    
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
    
The Software shall be used for Good, not Evil.
    
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
 * Represents a structured query ready for processing.
 * 
 * @author Surrey
 * 
 */
public class StructuredQuery {

    private QueryType queryType;
    private List<String> fields = new ArrayList<String>();
    private List<String> expectedFields = new ArrayList<String>();
    private List<Order> orders = new ArrayList<Order>();
    private List<String> groups = new ArrayList<String>();
    private Map<String, Projection> aggregates = new HashMap<String, Projection>();
    private Set<String> fieldSet = new HashSet<String>();
    private ProjectionList projections;
    private Criteria criteria;
    private int startIndex = 0;
    private int maxResults = 10;
    private boolean executed = false;
    private boolean hasJoin = false;
    private List<Join> joins = new ArrayList<Join>();
    private JoinType joinType = JoinType.PER_JOIN;

    /**
     * Creates a query of the given type for the object.
     * 
     * @param queryType
     *            The type of query.
     * @param data
     *            the hibernate entity to query
     * @param session
     * @param joins2
     */
    public StructuredQuery(QueryType queryType, ClassMetadata data, Session session, List<Join> joins) {
        this.queryType = queryType;
        if (queryType == QueryType.LOAD) {
            this.maxResults = 1000; // in case the load is a merged load
        }
        this.criteria = session.createCriteria(data.getEntityName());
        this.joins = joins;
        if (joins != null && joins.size() > 0) {
            hasJoin = true;
            for (Join join : joins) {
                join.setCriteria(session.createCriteria(data.getEntityName()));
                join.setSession(session);
                join.setEntity(data.getEntityName());
            }
        }
    }

    public void addField(String field) {
        fields.add(field);
    }

    public void addGroup(String group) {
        groups.add(group);
    }

    public void addWhere(Criterion createCriterion) {
        criteria.add(createCriterion);
        if (hasJoin) {
            for (Join join : joins) {
                join.getCriteria().add(createCriterion);
            }
        }
    }

    public void setHasJoin(boolean hasJoin) {
        this.hasJoin = hasJoin;
    }

    public QueryResult execute() throws ParserException {
        QueryResult result = new QueryResult();

        result.setResult(new ArrayList<Object>());
        result.setEndIndex(0);
        result.setStartIndex(0);
        if (!executed) {
            processGroups();
        }
        if (queryType == QueryType.COUNT) {
            if (!executed) {
                criteria.setProjection(Projections.rowCount());
            }
            Object obj = criteria.uniqueResult();
            if (obj != null) {
                result.getResult().add(obj);
            }
        } else if (queryType == QueryType.AVG) {
            if (!executed) {
                if (fields.size() == 0) {
                    throw new ParserException("You must specify a single field to average.");
                }
                criteria.setProjection(Projections.avg(fields.get(0)));
            }
            Object obj = criteria.uniqueResult();
            if (obj != null) {
                result.getResult().add(obj);
            }
        } else if (queryType == QueryType.SUM) {
            if (!executed) {
                if (fields.size() == 0) {
                    throw new ParserException("You must specify a single field to sum.");
                }
                criteria.setProjection(Projections.sum(fields.get(0)));
            }
            Object obj = criteria.uniqueResult();
            if (obj != null) {
                result.getResult().add(obj);
            }
        } else if (queryType == QueryType.LIST) {
            if (!executed) {
                processAggregates();
                processOrder();
                processFields();
            }
            int count = runQuery(criteria, result, maxResults);

            result.setStartIndex(startIndex);
            result.setEndIndex(startIndex + Math.min(count, maxResults));
            // merge joins
            if (hasJoin) {
                runJoin(result);
            }
        } else if (queryType == QueryType.LOAD) {
            if (!executed) {
                processFields();
                processOrder();
            }
            Object obj = criteria.uniqueResult();
            if (obj != null) {
                fillResult(result, obj);
            }
            result.setEndIndex(0);
            result.setStartIndex(0);
            result.setTotalResults(result.getResult().size());
            if (hasJoin) {
                runJoin(result);
            }
        }

        executed = true;
        return result;
    }

    private void runJoin(QueryResult result) {
        if (joinType == JoinType.PER_JOIN) {
            join(result.getResult());
        } else { // for each row run the join
            for (Object obj : result.getResult()) {
                List<Object> row = new ArrayList<Object>();
                row.add(obj);
                join(row);
            }
        }
    }

    private void join(List<Object> rows) {
        for (Join join : joins) {
            Set<Object> ids = new HashSet<Object>();
            for (Object obj : rows) {
                if (obj instanceof Map) {
                    ids.add(((Map<?, ?>) obj).get(join.getJoinId()));
                }
            }
            // prepare the join by setting the order and adding an "in"
            // clause
            join.prepare(ids);

            // if ids is size 1 then we are either doing a per row join or there is only 1 result to join to
            int firstRow = ids.size() == 1 ? join.getStartIndex() : 0;
            ScrollableResults scroll = join.getCriteria().scroll(ScrollMode.FORWARD_ONLY);

            if (scroll.setRowNumber(firstRow)) {
                do {
                    Object[] row = scroll.get();
                    mergeResult(rows, row, join);
                } while (scroll.next());
            }
            scroll.close();
        }
    }

    /**
     * @param result
     *            result.result is List<Map<String,Object>>
     * @param row
     * @param join
     */
    @SuppressWarnings("unchecked")
    private void mergeResult(List<Object> rows, Object[] row, Join join) {
        // get the join id value from result and row to see if they match
        Object resultId = null;
        for (Object obj : rows) {
            // obj is map
            if (obj instanceof Map) {
                Map<String, Object> resultMap = (Map<String, Object>) obj;
                resultId = resultMap.get(join.getJoinId());
                Object rowId = row[join.getExpectedFields().indexOf(join.getJoinId())];
                if (resultId != null && resultId.equals(rowId)) {
                    List<Map<String, Object>> joinField = (List<Map<String, Object>>) resultMap.get(join.getName());
                    if (joinField == null) {
                        joinField = new ArrayList<Map<String, Object>>();
                        resultMap.put(join.getName(), joinField);
                    }
                    Map<String, Object> values = new HashMap<String, Object>();
                    for (String field : join.getExpectedFields()) {
                        if (field.startsWith(join.getName())) {
                            String fieldName = field.substring(field.indexOf(".") + 1);
                            Object joinResult = row[join.getExpectedFields().indexOf(field)];
                            values.put(fieldName, joinResult);
                        }
                    }
                    joinField.add(values);
                }
            }
        }
    }

    private int runQuery(Criteria criteria, QueryResult result, int maxResults) {
        ScrollableResults scroll = criteria.scroll(ScrollMode.FORWARD_ONLY);
        int count = 0;
        if (scroll.setRowNumber(startIndex)) {
            while (count < maxResults) {
                Object[] row = scroll.get();
                count = fillResult(result, row) ? count += 1 : count;
                if (!scroll.next()) {
                    break;
                }
            }
        }
        int totalResultCount = 0;
        if (scroll.last()) {
            totalResultCount = scroll.getRowNumber() + 1;
        }
        result.setTotalResults(totalResultCount);
        scroll.close();
        return count;
    }

    private void processGroups() {
        if (groups.size() > 0) {
            if (projections == null) {
                projections = Projections.projectionList();
            }
            for (String group : groups) {
                projections.add(Projections.groupProperty(group));
                expectedFields.add(group);
                if (hasJoin) {
                    for (Join join : joins) {
                        join.addExpectedField(group);
                    }
                }
            }
        }
    }

    /**
     * @param result
     * @param obj
     * @return true if the result causes the row count to increment. ie: if this
     *         row is not a merged row.
     */
    private boolean fillResult(QueryResult result, Object obj) {
        boolean increment = true;
        if (expectedFields.size() > 0) {
            Map<String, Object> resultMap = new HashMap<String, Object>();
            for (int i = 0; i < expectedFields.size(); i++) {
                resultMap.put(expectedFields.get(i), Array.get(obj, i));
            }
            result.getResult().add(resultMap);
        } else {
            if (obj.getClass().isArray()) {
                result.getResult().add(Array.get(obj, 0));
            } else {
                result.getResult().add(obj);
            }
        }
        return increment;
    }

    private void processOrder() {
        if (orders.size() > 0) {
            for (Order order : orders) {
                criteria.addOrder(order);
                if (hasJoin) {
                    for (Join join : joins) {
                        join.getCriteria().addOrder(order);
                    }
                }
            }
        }
    }

    private void processAggregates() {
        if (aggregates.size() > 0) {
            if (projections == null) {
                projections = Projections.projectionList();
            }
            for (String prop : aggregates.keySet()) {
                projections.add(aggregates.get(prop));
                expectedFields.add(prop);
            }
        }
    }

    private void processFields() throws ParserException {
        if (fields.size() > 0) {
            if (projections == null) {
                projections = Projections.projectionList();
            }
            for (String field : fields) {
                projections.add(Projections.property(field));
                expectedFields.add(field);
            }
            criteria.setProjection(projections);
        }
        if (hasJoin) {
            for (Join join : joins) {
                if (join.getProjections() == null) {
                    join.setProjections(Projections.projectionList());
                }
                for (String field : fields) {
                    join.getProjections().add(Projections.property(field));
                    join.addExpectedField(field);
                }
                if (join.getFields().isEmpty()) {
                    // get all the properties from the joined object
                    try {
                        join.populateFields();
                    } catch (Exception e) {
                        throw new ParserException("failed to create join object: " + e);
                    }
                } else {
                    for (String field : join.getFields()) {
                        join.getProjections().add(Projections.property(field));
                        join.addExpectedField(field);
                    }
                }
                join.applyProjections();
            }
        }
    }

    public int getMaxResults() {
        return maxResults;
    }

    public void setMaxResults(int maxResults) {
        this.maxResults = maxResults;
    }

    public int getStartIndex() {
        return startIndex;
    }

    public void setStartIndex(int startIndex) {
        this.startIndex = startIndex;
    }

    public void createJoin(String join) {
        if (!fieldSet.contains(join)) {
            fieldSet.add(join);
            criteria.createCriteria(join, join);
            if (hasJoin) {
                for (Join joinObj : joins) {
                    joinObj.getCriteria().createCriteria(join, join);
                }
            }
        }
    }

    public void addOrder(String field, boolean ascending) {
        if (ascending) {
            orders.add(Order.asc(field));
        } else {
            orders.add(Order.desc(field));
        }
    }

    public void addAggregate(QueryType type, String field) throws ParserException {
        switch (type) {
        case AVG:
            aggregates.put("avg(" + field + ")", Projections.avg(field));
            break;
        case COUNT:
            aggregates.put("count(" + field + ")", Projections.count(field));
            break;
        case SUM:
            aggregates.put("sum(" + field + ")", Projections.sum(field));
            break;
        default:
            throw new ParserException("Invalid aggregate type: " + type.name());
        }
    }

    /**
     * @return the joinType
     */
    public JoinType getJoinType() {
        return joinType;
    }

    /**
     * @param joinType
     *            the joinType to set
     */
    public void setJoinType(JoinType joinType) {
        this.joinType = joinType;
    }

}