View Javadoc

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