/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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.jbpm.query.jpa.data; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; import javax.persistence.criteria.Predicate; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlEnum; import javax.xml.bind.annotation.XmlEnumValue; import javax.xml.bind.annotation.XmlRootElement; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * THIS CLASS SHOULD NEVER BE EXPOSED IN THE PUBLIC API!! * </p> * EXTERNAL USE OF THIS CLASS IS **NOT** SUPPORTED! * </p> * * This object can be seen as a (dynamic) representation of the <code>WHERE</code> part of a query. * </p> * It has the following responsibilities: <ol> * <li>Hold a list of the added query criteria </li> * <li>Keep track of the criteria preferences:<ul> * <li>Are we adding a range, a regexp or just a normal criteria?</li> * <li>Is this the start or end of a group?</li></ul> * </li> * </ol> */ @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) // @formatter:off @JsonIgnoreProperties({"union","type", // transient fields "currentGroupCriteria", "ancestry", "currentParent", "addedJoins"}) @JsonAutoDetect(fieldVisibility=Visibility.ANY, getterVisibility=Visibility.NONE, setterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE) // @formatter:on public class QueryWhere { @XmlEnum public static enum QueryCriteriaType { @XmlEnumValue("N") NORMAL, @XmlEnumValue("L") REGEXP, @XmlEnumValue("R") RANGE, @XmlEnumValue("R") GROUP, } @XmlElement(name="queryCriteria") private List<QueryCriteria> criteria = new LinkedList<QueryCriteria>(); @XmlElement private Boolean ascOrDesc = null; @XmlElement private String orderByListId = null; @XmlElement private Integer maxResults = null; @XmlElement private Integer offset = null; @JsonIgnore private transient boolean union = true; @JsonIgnore private transient QueryCriteriaType type = QueryCriteriaType.NORMAL; @JsonIgnore private transient List<QueryCriteria> currentCriteria = criteria; @JsonIgnore private transient Stack<Object> ancestry = new Stack<Object>(); @JsonIgnore private transient Object currentParent = this; @JsonIgnore private transient Map<String, Predicate> joinPredicates = null; public QueryWhere() { // JAXB constructor } // add logic /** * This method should be used for<ol> * <li>Normal parameters</li> * <li>Regular expression parameters</li> * </ol> * This method should <b>not</b> be used for<ol> * <li>Range parameters</li> * </ol> * @param listId * @param param * @return */ public <T> QueryCriteria addParameter( String listId, T... param ) { if( param.length == 0 ) { return null; } if( QueryCriteriaType.REGEXP.equals(this.type) && ! (param[0] instanceof String) ) { throw new IllegalArgumentException("Only String parameters may be used in regular expressions."); } QueryCriteria criteria = new QueryCriteria(listId, this.union, this.type, param.length); for( T paramElem : param ) { criteria.addParameter(paramElem); } addCriteria(criteria); return criteria; } public <T> void addRangeParameter( String listId, T param, boolean start ) { QueryCriteriaType origType = this.type; this.type = QueryCriteriaType.RANGE; //should be the same as before! QueryCriteria criteria = new QueryCriteria(listId, this.union, this.type, 2); int index = start ? 0 : 1; criteria.setParameter(index, param, 2); addCriteria(criteria); this.type = origType; } public <T> void addRangeParameters( String listId, T paramMin, T paramMax ) { QueryCriteriaType origType = this.type; this.type = QueryCriteriaType.RANGE; //should be the same as before! QueryCriteria criteria = new QueryCriteria(listId, this.union, this.type, 2); criteria.addParameter(paramMin); criteria.addParameter(paramMax); addCriteria(criteria); this.type = origType; } private void addCriteria(QueryCriteria criteria) { if( this.currentCriteria.isEmpty() ) { criteria.setFirst(true); } else if( this.currentCriteria.size() == 1 ) { this.currentCriteria.get(0).setUnion(criteria.isUnion()); } this.currentCriteria.add(criteria); } // group management public void newGroup() { // create parent QueryCriteria newCriteriaGroupParent = new QueryCriteria(this.union); addCriteria(newCriteriaGroupParent); // add parent to parent stack ancestry.push(currentParent); currentParent = newCriteriaGroupParent; // set group criteria list to new list currentCriteria = newCriteriaGroupParent.getCriteria(); } public void endGroup() { if( ancestry.isEmpty() ) { throw new IllegalStateException("Can not end group: no group has been started!"); } // set current group criteria to point to correct list Object grandparent = ancestry.pop(); if( grandparent instanceof QueryWhere ) { currentCriteria = ((QueryWhere) grandparent).getCriteria(); } else { currentCriteria = ((QueryCriteria) grandparent).getCriteria(); } currentParent = grandparent; } @JsonIgnore public void setAscending( String listId ) { this.ascOrDesc = true; this.orderByListId = listId; } @JsonIgnore public void setDescending( String listId ) { this.ascOrDesc = false; this.orderByListId = listId; } public List<QueryCriteria> getCurrentCriteria() { return currentCriteria; } // getters & setters public List<QueryCriteria> getCriteria() { return criteria; } public void setCriteria(List<QueryCriteria> criteria) { this.criteria = criteria; } public void setParameters( List<QueryCriteria> parameters ) { this.criteria = parameters; } public void setAscOrDesc( Boolean ascendingOrDescending ) { this.ascOrDesc = ascendingOrDescending; } public Boolean getAscOrDesc() { return this.ascOrDesc; } public void setOrderByListId( String listId ) { this.orderByListId = listId; } public String getOrderByListId() { return this.orderByListId; } public void setCount( Integer maxResults ) { this.maxResults = maxResults; } public Integer getCount() { return this.maxResults; } public void setOffset( Integer offset ) { this.offset = offset; } public Integer getOffset() { return this.offset; } public QueryCriteriaType getCriteriaType() { return this.type; } public void setToUnion() { this.union = true; } public void setToIntersection() { this.union = false; } public boolean isUnion() { return this.union; } public void setToLike() { this.type = QueryCriteriaType.REGEXP; } public boolean isLike() { return this.type.equals(QueryCriteriaType.REGEXP); } public void setToNormal() { this.type = QueryCriteriaType.NORMAL; } public void setToRange() { this.type = QueryCriteriaType.RANGE; } public boolean isRange() { return this.type.equals(QueryCriteriaType.RANGE); } public void setToGroup() { this.type = QueryCriteriaType.GROUP; } public Map<String, Predicate> getJoinPredicates() { if( this.joinPredicates == null ) { this.joinPredicates = new HashMap<String, Predicate>(3); } return this.joinPredicates; } // clear & clone public void clear() { this.union = true; this.type = QueryCriteriaType.NORMAL; this.ancestry.clear(); if( this.criteria != null ) { this.criteria.clear(); } this.currentCriteria = this.criteria; this.maxResults = null; this.offset = null; this.orderByListId = null; this.ascOrDesc = null; this.joinPredicates = null; } public QueryWhere(QueryWhere queryWhere) { this.union = queryWhere.union; this.type = queryWhere.type; if( queryWhere.criteria != null ) { this.criteria = new LinkedList<QueryCriteria>(queryWhere.criteria); } this.ascOrDesc = queryWhere.ascOrDesc; this.orderByListId = queryWhere.orderByListId; this.maxResults = queryWhere.maxResults; this.offset = queryWhere.offset; this.joinPredicates = queryWhere.joinPredicates; } }