View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.core.api.criteria;
17  
18  import org.kuali.rice.core.api.CoreConstants;
19  import org.kuali.rice.core.api.mo.AbstractDataTransferObject;
20  import org.kuali.rice.core.api.mo.ModelBuilder;
21  import org.kuali.rice.core.api.util.collect.CollectionUtils;
22  import org.w3c.dom.Element;
23  
24  import javax.xml.bind.annotation.XmlAccessType;
25  import javax.xml.bind.annotation.XmlAccessorType;
26  import javax.xml.bind.annotation.XmlAnyElement;
27  import javax.xml.bind.annotation.XmlElement;
28  import javax.xml.bind.annotation.XmlElementWrapper;
29  import javax.xml.bind.annotation.XmlElements;
30  import javax.xml.bind.annotation.XmlRootElement;
31  import javax.xml.bind.annotation.XmlType;
32  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
33  import java.io.Serializable;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.List;
38  
39  /**
40   * Defines a criteria-based query.  Consists of a {@link Predicate} definition
41   * as well as a set of additional properties which control paging and other
42   * aspects of the results which should be returned from the query.
43   * 
44   * <p>In order to construct a new {@link QueryByCriteria}, the {@link Builder}
45   * should be used.  Use the {@link PredicateFactory} to construct
46   * the predicate for use by the query.
47   * 
48   * <p>This class specifies nothing regarding how the query will be executed.
49   * It is expected that an instance will be constructed and then passed to code
50   * which understands how to execute the desired query.
51   * 
52   * <p>This class is mapped for use by JAXB and can therefore be used by clients
53   * as part of remotable service definitions.
54   * 
55   * @see Predicate
56   * @see PredicateFactory
57   * 
58   * @author Kuali Rice Team (rice.collab@kuali.org)
59   * 
60   */
61  @XmlRootElement(name = QueryByCriteria.Constants.ROOT_ELEMENT_NAME)
62  @XmlAccessorType(XmlAccessType.NONE)
63  @XmlType(name = QueryByCriteria.Constants.TYPE_NAME, propOrder = {
64  		QueryByCriteria.Elements.PREDICATE,
65  		QueryByCriteria.Elements.START_AT_INDEX,
66  		QueryByCriteria.Elements.MAX_RESULTS,
67  		QueryByCriteria.Elements.COUNT_FLAG,
68          QueryByCriteria.Elements.ORDER_BY_FIELDS,
69  		CoreConstants.CommonElements.FUTURE_ELEMENTS })
70  public final class QueryByCriteria extends AbstractDataTransferObject {
71  
72  	private static final long serialVersionUID = 2210627777648920180L;
73  
74      @XmlElements(value = {
75          @XmlElement(name = AndPredicate.Constants.ROOT_ELEMENT_NAME, type = AndPredicate.class, required = false),
76          @XmlElement(name = EqualPredicate.Constants.ROOT_ELEMENT_NAME, type = EqualPredicate.class, required = false),
77          @XmlElement(name = EqualIgnoreCasePredicate.Constants.ROOT_ELEMENT_NAME, type = EqualIgnoreCasePredicate.class, required = false),
78          @XmlElement(name = GreaterThanPredicate.Constants.ROOT_ELEMENT_NAME, type = GreaterThanPredicate.class, required = false),
79          @XmlElement(name = GreaterThanOrEqualPredicate.Constants.ROOT_ELEMENT_NAME, type = GreaterThanOrEqualPredicate.class, required = false),
80          @XmlElement(name = InPredicate.Constants.ROOT_ELEMENT_NAME, type = InPredicate.class, required = false),
81          @XmlElement(name = InIgnoreCasePredicate.Constants.ROOT_ELEMENT_NAME, type = InIgnoreCasePredicate.class, required = false),
82          @XmlElement(name = LessThanPredicate.Constants.ROOT_ELEMENT_NAME, type = LessThanPredicate.class, required = false),
83          @XmlElement(name = LessThanOrEqualPredicate.Constants.ROOT_ELEMENT_NAME, type = LessThanOrEqualPredicate.class, required = false),
84          @XmlElement(name = LikePredicate.Constants.ROOT_ELEMENT_NAME, type = LikePredicate.class, required = false),
85          @XmlElement(name = NotEqualPredicate.Constants.ROOT_ELEMENT_NAME, type = NotEqualPredicate.class, required = false),
86          @XmlElement(name = NotEqualIgnoreCasePredicate.Constants.ROOT_ELEMENT_NAME, type = NotEqualIgnoreCasePredicate.class, required = false),
87          @XmlElement(name = NotInPredicate.Constants.ROOT_ELEMENT_NAME, type = NotInPredicate.class, required = false),
88          @XmlElement(name = NotInIgnoreCasePredicate.Constants.ROOT_ELEMENT_NAME, type = NotInIgnoreCasePredicate.class, required = false),
89          @XmlElement(name = NotLikePredicate.Constants.ROOT_ELEMENT_NAME, type = NotLikePredicate.class, required = false),
90          @XmlElement(name = NotNullPredicate.Constants.ROOT_ELEMENT_NAME, type = NotNullPredicate.class, required = false),
91          @XmlElement(name = NullPredicate.Constants.ROOT_ELEMENT_NAME, type = NullPredicate.class, required = false),
92          @XmlElement(name = OrPredicate.Constants.ROOT_ELEMENT_NAME, type = OrPredicate.class, required = false)
93      })
94  	private final Predicate predicate;
95  	
96  	@XmlElement(name = Elements.START_AT_INDEX, required = false)
97  	private final Integer startAtIndex;
98  		
99  	@XmlElement(name = Elements.MAX_RESULTS, required = false)
100 	private final Integer maxResults;
101 	
102 	@XmlJavaTypeAdapter(CountFlag.Adapter.class)
103 	@XmlElement(name = Elements.COUNT_FLAG, required = true)
104 	private final String countFlag;
105 
106     @XmlElementWrapper(name = Elements.ORDER_BY_FIELDS, required = false)
107     @XmlElement(name = Elements.ORDER_BY_FIELD, required = false)
108     private final List<OrderByField> orderByFields;
109 
110 
111 	@SuppressWarnings("unused")
112 	@XmlAnyElement
113 	private final Collection<Element> _futureElements = null;
114 
115 	private QueryByCriteria() {
116 		this.predicate = null;
117 		this.startAtIndex = null;
118 		this.maxResults = null;
119 		this.countFlag = null;
120         this.orderByFields = null;
121 	}
122 
123 	private QueryByCriteria(Builder builder) {
124 		final Predicate[] preds = builder.predicates;
125         if (preds != null && preds.length > 1) {
126             //implicit "and"
127             this.predicate = PredicateFactory.and(builder.predicates);
128         } else if (preds != null && preds.length == 1) {
129             this.predicate = builder.predicates[0];
130         } else {
131             this.predicate = null;
132         }
133 
134 		this.startAtIndex = builder.getStartAtIndex();
135 		this.maxResults = builder.getMaxResults();
136 		this.countFlag = builder.getCountFlag() == null ? null : builder.getCountFlag().getFlag();
137         this.orderByFields = new ArrayList<OrderByField>(builder.getOrderByFields());
138 	}
139 
140 	/**
141 	 * Returns the {@link Predicate} which will be used to execute the query.
142 	 * 
143 	 * @return can be null if no predicate was specified
144 	 */
145 	public Predicate getPredicate() {
146 		return this.predicate;
147 	}
148 
149 	/**
150 	 * Returns the optional zero-based "start" index for rows returned.  When
151 	 * this query is executed, this property should be read to determine the
152 	 * first row which should be returned.  If the given index is beyond the
153 	 * end of the result set, then the resulting query should effectively
154 	 * return no rows (as opposed to producing an index-based out of bounds
155 	 * error).  If the value is null, then the results should start with the
156 	 * first row returned from the query.
157 	 *
158      * <p>
159      * Will never be less than 0
160      *
161 	 * @return the starting row index requested by this query, or null if
162 	 * the results should start at the beginning of the result set
163 	 */
164 	public Integer getStartAtIndex() {
165 		return this.startAtIndex;
166 	}
167 
168 	/**
169 	 * Returns the maximum number of results that this query is requesting
170 	 * to receive.  If null, then the query should return all rows, or as
171 	 * many as it can.  If the number request is larger than the number of
172      * results then all results are returned.
173 	 *
174      * <p>
175      * Will never be less than 0
176      *
177 	 * @return the maximum number of results to return from the query
178 	 */
179 	public Integer getMaxResults() {
180 		return this.maxResults;
181 	}
182 
183 	/**
184 	 * Indicates whether or not a total row count should be returned with the
185 	 * query.  See {@link CountFlag} for more information on what each of these
186 	 * flags means.  This will never return null and defaults to
187 	 * {@link CountFlag#NONE}.
188 	 * 
189 	 * @return the flag specifying whether or not a total row count should be
190 	 * produced by the query
191 	 */
192 	public CountFlag getCountFlag() {
193 		return this.countFlag == null ? null : CountFlag.valueOf(this.countFlag);
194 	}
195 
196     /**
197      * Returns the a list of fields that will be ordered depending on the orderDirection
198      * when results are returned.
199      *
200      * @return List of field names that will affect the order of the returned rows
201      */
202     public List<OrderByField> getOrderByFields() {
203         return CollectionUtils.unmodifiableListNullSafe(this.orderByFields);
204     }
205 
206 	public static final class Builder implements ModelBuilder, Serializable {
207 
208 		private Predicate[] predicates;
209 		private Integer startAtIndex;
210 		private Integer maxResults;
211 		private CountFlag countFlag;
212         private List<OrderByField> orderByFields;
213 
214 		private Builder() {
215 			setCountFlag(CountFlag.NONE);
216             setOrderByFields(new ArrayList<OrderByField>());
217 		}
218 
219 		public static Builder create() {
220             return new Builder();
221 		}
222 
223 		public Integer getStartAtIndex() {
224             return this.startAtIndex;
225 		}
226 
227 		public void setStartAtIndex(Integer startAtIndex) {
228             if (startAtIndex != null && startAtIndex < 0) {
229                 throw new IllegalArgumentException("startAtIndex < 0");
230             }
231 
232             this.startAtIndex = startAtIndex;
233 		}
234 
235 		public Integer getMaxResults() {
236 			return this.maxResults;
237 		}
238 
239 		public void setMaxResults(Integer maxResults) {
240 			if (maxResults != null && maxResults < 0) {
241                 throw new IllegalArgumentException("maxResults < 0");
242             }
243 
244             this.maxResults = maxResults;
245 		}
246 
247 		public CountFlag getCountFlag() {
248 			return this.countFlag;
249 		}
250 
251 		public void setCountFlag(CountFlag countFlag) {
252 			if (countFlag == null) {
253                 throw new IllegalArgumentException("countFlag was null");
254             }
255 
256             this.countFlag = countFlag;
257 		}
258 
259         public List<OrderByField> getOrderByFields() {
260             return this.orderByFields;
261         }
262 
263         public void setOrderByFields(List<OrderByField> orderByFields) {
264             if (orderByFields == null) {
265                 throw new IllegalArgumentException("orderByFields was null");
266             }
267             this.orderByFields = orderByFields;
268         }
269 
270         /**
271          * will return an array of the predicates.  may return null if no predicates were set.
272          * @return the predicates
273          */
274 		public Predicate[] getPredicates() {
275 			if (this.predicates == null) {
276                 return null;
277             }
278 
279 			//defensive copies on array
280             return Arrays.copyOf(predicates, predicates.length);
281 		}
282 
283         /**
284          * Sets the predicates. If multiple predicates are specified then they are wrapped
285          * in an "and" predicate. If a null predicate is specified then there will be no
286          * constraints on the query.
287          * @param predicates the predicates to set.
288          */
289         public void setPredicates(Predicate... predicates) {
290             //defensive copies on array
291             this.predicates = predicates != null ? Arrays.copyOf(predicates, predicates.length) : null;
292 		}
293 
294         @Override
295         public QueryByCriteria build() {
296             return new QueryByCriteria(this);
297         }
298 
299         /** convenience method to create an immutable criteria from one or more predicates. */
300         public static QueryByCriteria fromPredicates(Predicate... predicates) {
301             final Builder b = Builder.create();
302             b.setPredicates(predicates);
303             return b.build();
304         }
305     }
306 
307 	/**
308 	 * Defines some internal constants used on this class.
309 	 */
310 	static class Constants {
311 		final static String ROOT_ELEMENT_NAME = "queryByCriteria";
312 		final static String TYPE_NAME = "QueryByCriteriaType";
313 	}
314 
315 	/**
316 	 * A private class which exposes constants which define the XML element
317 	 * names to use when this object is marshaled to XML.
318 	 */
319 	static class Elements {
320 		final static String PREDICATE = "predicate";
321 		final static String START_AT_INDEX = "startAtIndex";
322 		final static String MAX_RESULTS = "maxResults";
323 		final static String COUNT_FLAG = "countFlag";
324         final static String ORDER_BY_FIELDS = "orderByFields";
325         final static String ORDER_BY_FIELD = "orderByField";
326 	}
327 
328 }