View Javadoc

1   /**
2    * Copyright 2005-2012 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 = GreaterThanPredicate.Constants.ROOT_ELEMENT_NAME, type = GreaterThanPredicate.class, required = false),
73          @XmlElement(name = GreaterThanOrEqualPredicate.Constants.ROOT_ELEMENT_NAME, type = GreaterThanOrEqualPredicate.class, required = false),
74          @XmlElement(name = InPredicate.Constants.ROOT_ELEMENT_NAME, type = InPredicate.class, required = false),
75          @XmlElement(name = LessThanPredicate.Constants.ROOT_ELEMENT_NAME, type = LessThanPredicate.class, required = false),
76          @XmlElement(name = LessThanOrEqualPredicate.Constants.ROOT_ELEMENT_NAME, type = LessThanOrEqualPredicate.class, required = false),
77          @XmlElement(name = LikePredicate.Constants.ROOT_ELEMENT_NAME, type = LikePredicate.class, required = false),
78          @XmlElement(name = NotEqualPredicate.Constants.ROOT_ELEMENT_NAME, type = NotEqualPredicate.class, required = false),
79          @XmlElement(name = NotInPredicate.Constants.ROOT_ELEMENT_NAME, type = NotInPredicate.class, required = false),
80          @XmlElement(name = NotLikePredicate.Constants.ROOT_ELEMENT_NAME, type = NotLikePredicate.class, required = false),
81          @XmlElement(name = NotNullPredicate.Constants.ROOT_ELEMENT_NAME, type = NotNullPredicate.class, required = false),
82          @XmlElement(name = NullPredicate.Constants.ROOT_ELEMENT_NAME, type = NullPredicate.class, required = false),
83          @XmlElement(name = OrPredicate.Constants.ROOT_ELEMENT_NAME, type = OrPredicate.class, required = false)
84      })
85  	private final Predicate predicate;
86  	
87  	@XmlElement(name = Elements.START_AT_INDEX, required = false)
88  	private final Integer startAtIndex;
89  		
90  	@XmlElement(name = Elements.MAX_RESULTS, required = false)
91  	private final Integer maxResults;
92  	
93  	@XmlJavaTypeAdapter(CountFlag.Adapter.class)
94  	@XmlElement(name = Elements.COUNT_FLAG, required = true)
95  	private final String countFlag;
96  
97  	@SuppressWarnings("unused")
98  	@XmlAnyElement
99  	private final Collection<Element> _futureElements = null;
100 
101 	private QueryByCriteria() {
102 		this.predicate = null;
103 		this.startAtIndex = null;
104 		this.maxResults = null;
105 		this.countFlag = null;
106 	}
107 
108 	private QueryByCriteria(Builder builder) {
109 		final Predicate[] preds = builder.predicates;
110         if (preds != null && preds.length > 1) {
111             //implicit "and"
112             this.predicate = PredicateFactory.and(builder.predicates);
113         } else if (preds != null && preds.length == 1) {
114             this.predicate = builder.predicates[0];
115         } else {
116             this.predicate = null;
117         }
118 
119 		this.startAtIndex = builder.getStartAtIndex();
120 		this.maxResults = builder.getMaxResults();
121 		this.countFlag = builder.getCountFlag() == null ? null : builder.getCountFlag().getFlag();
122 	}
123 
124 	/**
125 	 * Returns the {@link Predicate} which will be used to execute the query.
126 	 * 
127 	 * @return can be null if no predicate was specified
128 	 */
129 	public Predicate getPredicate() {
130 		return this.predicate;
131 	}
132 
133 	/**
134 	 * Returns the optional zero-based "start" index for rows returned.  When
135 	 * this query is executed, this property should be read to determine the
136 	 * first row which should be returned.  If the given index is beyond the
137 	 * end of the result set, then the resulting query should effectively
138 	 * return no rows (as opposed to producing an index-based out of bounds
139 	 * error).  If the value is null, then the results should start with the
140 	 * first row returned from the query.
141 	 *
142      * <p>
143      * Will never be less than 0
144      *
145 	 * @return the starting row index requested by this query, or null if
146 	 * the results should start at the beginning of the result set
147 	 */
148 	public Integer getStartAtIndex() {
149 		return this.startAtIndex;
150 	}
151 
152 	/**
153 	 * Returns the maximum number of results that this query is requesting
154 	 * to receive.  If null, then the query should return all rows, or as
155 	 * many as it can.  If the number request is larger than the number of
156      * results then all results are returned.
157 	 *
158      * <p>
159      * Will never be less than 0
160      *
161 	 * @return the maximum number of results to return from the query
162 	 */
163 	public Integer getMaxResults() {
164 		return this.maxResults;
165 	}
166 
167 	/**
168 	 * Indicates whether or not a total row count should be returned with the
169 	 * query.  See {@link CountFlag} for more information on what each of these
170 	 * flags means.  This will never return null and defaults to
171 	 * {@link CountFlag#NONE}.
172 	 * 
173 	 * @return the flag specifying whether or not a total row count should be
174 	 * produced by the query
175 	 */
176 	public CountFlag getCountFlag() {
177 		return this.countFlag == null ? null : CountFlag.valueOf(this.countFlag);
178 	}
179 
180 	public static final class Builder implements ModelBuilder, Serializable {
181 
182 		private Predicate[] predicates;
183 		private Integer startAtIndex;
184 		private Integer maxResults;
185 		private CountFlag countFlag;
186 
187 		private Builder() {
188 			this.countFlag = CountFlag.NONE;
189 		}
190 
191 		public static Builder create() {
192             return new Builder();
193 		}
194 
195 		public Integer getStartAtIndex() {
196             return this.startAtIndex;
197 		}
198 
199 		public void setStartAtIndex(Integer startAtIndex) {
200             if (startAtIndex != null && startAtIndex < 0) {
201                 throw new IllegalArgumentException("startAtIndex < 0");
202             }
203 
204             this.startAtIndex = startAtIndex;
205 		}
206 
207 		public Integer getMaxResults() {
208 			return this.maxResults;
209 		}
210 
211 		public void setMaxResults(Integer maxResults) {
212 			if (maxResults != null && maxResults < 0) {
213                 throw new IllegalArgumentException("maxResults < 0");
214             }
215 
216             this.maxResults = maxResults;
217 		}
218 
219 		public CountFlag getCountFlag() {
220 			return this.countFlag;
221 		}
222 
223 		public void setCountFlag(CountFlag countFlag) {
224 			if (countFlag == null) {
225                 throw new IllegalArgumentException("countFlag was null");
226             }
227 
228             this.countFlag = countFlag;
229 		}
230 
231         /**
232          * will return an array of the predicates.  may return null if no predicates were set.
233          * @return the predicates
234          */
235 		public Predicate[] getPredicates() {
236 			if (this.predicates == null) {
237                 return null;
238             }
239 
240 			//defensive copies on array
241             return Arrays.copyOf(predicates, predicates.length);
242 		}
243 
244         /**
245          * Sets the predicates. If multiple predicates are specified then they are wrapped
246          * in an "and" predicate. If a null predicate is specified then there will be no
247          * constraints on the query.
248          * @param predicates the predicates to set.
249          */
250         public void setPredicates(Predicate... predicates) {
251             //defensive copies on array
252             this.predicates = predicates != null ? Arrays.copyOf(predicates, predicates.length) : null;
253 		}
254 
255         @Override
256         public QueryByCriteria build() {
257             return new QueryByCriteria(this);
258         }
259 
260         /** convenience method to create an immutable criteria from one or more predicates. */
261         public static QueryByCriteria fromPredicates(Predicate... predicates) {
262             final Builder b = Builder.create();
263             b.setPredicates(predicates);
264             return b.build();
265         }
266     }
267 
268 	/**
269 	 * Defines some internal constants used on this class.
270 	 */
271 	static class Constants {
272 		final static String ROOT_ELEMENT_NAME = "queryByCriteria";
273 		final static String TYPE_NAME = "QueryByCriteriaType";
274 	}
275 
276 	/**
277 	 * A private class which exposes constants which define the XML element
278 	 * names to use when this object is marshaled to XML.
279 	 */
280 	static class Elements {
281 		final static String PREDICATE = "predicate";
282 		final static String START_AT_INDEX = "startAtIndex";
283 		final static String MAX_RESULTS = "maxResults";
284 		final static String COUNT_FLAG = "countFlag";
285 	}
286 
287 }