1 package org.apache.ojb.broker.query;
2
3 /* Copyright 2002-2005 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24
25 import org.apache.ojb.broker.metadata.ClassDescriptor;
26 import org.apache.ojb.broker.metadata.FieldDescriptor;
27 import org.apache.ojb.broker.metadata.FieldHelper;
28 import org.apache.ojb.broker.metadata.MetadataManager;
29 import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
30 import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
31 import org.apache.ojb.broker.util.logging.LoggerFactory;
32
33 /**
34 * represents a search by criteria.
35 * "find all articles where article.price > 100"
36 * could be represented as:
37 *
38 * Criteria crit = new Criteria();
39 * crit.addGreaterThan("price", new Double(100));
40 * Query qry = new QueryByCriteria(Article.class, crit);
41 *
42 * The PersistenceBroker can retrieve Objects by Queries as follows:
43 *
44 * PersistenceBroker broker = PersistenceBrokerFactory.createPersistenceBroker();
45 * Collection col = broker.getCollectionByQuery(qry);
46 *
47 * Creation date: (24.01.2001 21:45:46)
48 * @author Thomas Mahler
49 * @version $Id: QueryByCriteria.java,v 1.1 2007-08-24 22:17:36 ewestfal Exp $
50 */
51 public class QueryByCriteria extends AbstractQueryImpl
52 {
53 private Criteria m_criteria;
54 private boolean m_distinct = false;
55 private Map m_pathClasses;
56 private Criteria m_havingCriteria;
57 private String m_objectProjectionAttribute;
58
59 // holding FieldHelper for orderBy and groupBy
60 private List m_orderby = null;
61 private List m_groupby = null;
62
63 // list of names of prefetchable relationships
64 private List m_prefetchedRelationships = null;
65
66 private Collection m_pathOuterJoins = null;
67
68 /**
69 * handy criteria that can be used to select all instances of
70 * a class.
71 */
72 public static final Criteria CRITERIA_SELECT_ALL = null;
73
74 /**
75 * Build a Query for class targetClass with criteria.
76 * Criteriy may be null (will result in a query returning ALL objects from a table)
77 */
78 public QueryByCriteria(Class targetClass, Criteria whereCriteria, Criteria havingCriteria, boolean distinct)
79 {
80 super (targetClass);
81
82 setCriteria(whereCriteria);
83 setHavingCriteria(havingCriteria);
84
85 m_distinct = distinct;
86 m_pathClasses = new HashMap();
87 m_groupby = new ArrayList();
88 m_orderby = new ArrayList();
89 m_prefetchedRelationships = new ArrayList();
90 m_pathOuterJoins = new HashSet();
91 }
92
93 /**
94 * Build a Query for class targetClass with criteria.
95 * Criteriy may be null (will result in a query returning ALL objects from a table)
96 */
97 public QueryByCriteria(Class targetClass, Criteria whereCriteria, Criteria havingCriteria)
98 {
99 this(targetClass, whereCriteria, havingCriteria, false);
100 }
101
102
103 /**
104 * Build a Query for class targetClass with criteria.
105 * Criteriy may be null (will result in a query returning ALL objects from a table)
106 */
107 public QueryByCriteria(Class targetClass, Criteria criteria)
108 {
109 this(targetClass, criteria, false);
110 }
111
112 /**
113 * Build a Query for class targetClass with criteria.
114 * Criteriy may be null (will result in a query returning ALL objects from a table)
115 */
116 public QueryByCriteria(Class targetClass, Criteria criteria, boolean distinct)
117 {
118 this(targetClass, criteria, null, distinct);
119 }
120
121 /**
122 * Build a Query based on anObject <br>
123 * all non null values are used as EqualToCriteria
124 */
125 public QueryByCriteria(Object anObject, boolean distinct)
126 {
127 this(anObject.getClass(), buildCriteria(anObject), distinct);
128 }
129
130 /**
131 * Build a Query based on anObject <br>
132 * all non null values are used as EqualToCriteria
133 */
134 public QueryByCriteria(Object anObject)
135 {
136 this(anObject.getClass(), buildCriteria(anObject));
137 }
138
139 /**
140 * Build a Query based on a Class Object. This
141 * Query will return all instances of the given class.
142 * @param aClassToSearchFrom the class to search from
143 */
144 public QueryByCriteria(Class aClassToSearchFrom)
145 {
146 this(aClassToSearchFrom, CRITERIA_SELECT_ALL);
147 }
148
149 /**
150 * Build Criteria based on example object<br>
151 * all non null values are used as EqualToCriteria
152 */
153 private static Criteria buildCriteria(Object anExample)
154 {
155 Criteria criteria = new Criteria();
156 ClassDescriptor cld = MetadataManager.getInstance().getRepository().getDescriptorFor(anExample.getClass());
157 FieldDescriptor[] fds = cld.getFieldDescriptions();
158 PersistentField f;
159 Object value;
160
161 for (int i = 0; i < fds.length; i++)
162 {
163 try
164 {
165 f = fds[i].getPersistentField();
166 value = f.get(anExample);
167 if (value != null)
168 {
169 criteria.addEqualTo(f.getName(), value);
170 }
171 }
172 catch (Throwable ex)
173 {
174 LoggerFactory.getDefaultLogger().error(ex);
175 }
176 }
177
178 return criteria;
179 }
180
181 /**
182 * Add a hint Class for a path. Used for relationships to extents.<br>
183 * SqlStatment will use these hint classes when resolving the path.
184 * Without these hints SqlStatment will use the base class the
185 * relationship points to ie: Article instead of CdArticle.
186 *
187 * @param aPath the path segment ie: allArticlesInGroup
188 * @param aClass the Class ie: CdArticle
189 * @see org.apache.ojb.broker.QueryTest#testInversePathExpression()
190 */
191 public void addPathClass(String aPath, Class aClass)
192 {
193 List pathClasses = (List) m_pathClasses.get(aPath);
194 if(pathClasses == null)
195 {
196 setPathClass(aPath, aClass);
197 }
198 else
199 {
200 pathClasses.add(aClass);
201 }
202 }
203
204 /**
205 * Set the Class for a path. Used for relationships to extents.<br>
206 * SqlStatment will use this class when resolving the path.
207 * Without this hint SqlStatment will use the base class the
208 * relationship points to ie: Article instead of CdArticle.
209 * Using this method is the same as adding just one hint
210 *
211 * @param aPath the path segment ie: allArticlesInGroup
212 * @param aClass the Class ie: CdArticle
213 * @see org.apache.ojb.broker.QueryTest#testInversePathExpression()
214 * @see #addPathClass
215 */
216 public void setPathClass(String aPath, Class aClass)
217 {
218 List pathClasses = new ArrayList();
219 pathClasses.add(aClass);
220 m_pathClasses.put(aPath, pathClasses);
221 }
222
223 /**
224 * Get the a List of Class objects used as hints for a path
225 *
226 * @param aPath the path segment ie: allArticlesInGroup
227 * @return a List o Class objects to be used in SqlStatment
228 * @see #addPathClass
229 * @see org.apache.ojb.broker.QueryTest#testInversePathExpression()
230 */
231 public List getClassesForPath(String aPath)
232 {
233 return (List)m_pathClasses.get(aPath);
234 }
235
236 /**
237 * Answer true if outer join for path should be used.
238 * @param aPath the path to query the outer join setting for
239 * @return true for outer join
240 */
241 public boolean isPathOuterJoin(String aPath)
242 {
243 return getOuterJoinPaths().contains(aPath);
244 }
245
246 /**
247 * Force outer join for the last segment of the path.
248 * ie. path = 'a.b.c' the outer join will be applied only to the relationship from B to C.
249 * if multiple segments need an outer join, setPathOuterJoin needs to be called for each segement.
250 * @param aPath force outer join to the last segment of this path
251 */
252 public void setPathOuterJoin(String aPath)
253 {
254 getOuterJoinPaths().add(aPath);
255 }
256
257 /* (non-Javadoc)
258 * @see org.apache.ojb.broker.query.Query#getCriteria()
259 */
260 public Criteria getCriteria()
261 {
262 return m_criteria;
263 }
264
265 /* (non-Javadoc)
266 * @see org.apache.ojb.broker.query.Query#getHavingCriteria()
267 */
268 public Criteria getHavingCriteria()
269 {
270 return m_havingCriteria;
271 }
272
273 /**
274 * Insert the method's description here.
275 * Creation date: (07.02.2001 22:01:55)
276 * @return java.lang.String
277 */
278 public String toString()
279 {
280 StringBuffer buf = new StringBuffer("QueryByCriteria from ");
281 buf.append(getSearchClass()).append(" ");
282 if (getCriteria() != null && !getCriteria().isEmpty())
283 {
284 buf.append(" where ").append(getCriteria());
285 }
286 return buf.toString();
287 }
288
289 /**
290 * Gets the distinct.
291 * @return Returns a boolean
292 */
293 public boolean isDistinct()
294 {
295 return m_distinct;
296 }
297
298 /**
299 * Sets the distinct.
300 * @param distinct The distinct to set
301 */
302 public void setDistinct(boolean distinct)
303 {
304 this.m_distinct = distinct;
305 }
306
307 /**
308 * Gets the pathClasses.
309 * A Map containing hints about what Class to be used for what path segment
310 * @return Returns a Map
311 */
312 public Map getPathClasses()
313 {
314 return m_pathClasses;
315 }
316
317 /**
318 * Sets the criteria.
319 * @param criteria The criteria to set
320 */
321 public void setCriteria(Criteria criteria)
322 {
323 m_criteria = criteria;
324 if (m_criteria != null)
325 {
326 m_criteria.setQuery(this);
327 }
328 }
329
330 /**
331 * Sets the havingCriteria.
332 * @param havingCriteria The havingCriteria to set
333 */
334 public void setHavingCriteria(Criteria havingCriteria)
335 {
336 m_havingCriteria = havingCriteria;
337 if (m_havingCriteria != null)
338 {
339 m_havingCriteria.setQuery(this);
340 }
341 }
342
343 /**
344 * Adds a groupby fieldName for ReportQueries.
345 * @param fieldName The groupby to set
346 */
347 public void addGroupBy(String fieldName)
348 {
349 if (fieldName != null)
350 {
351 m_groupby.add(new FieldHelper(fieldName, false));
352 }
353 }
354
355 /**
356 * Adds a field for groupby
357 * @param aField
358 */
359 public void addGroupBy(FieldHelper aField)
360 {
361 if (aField != null)
362 {
363 m_groupby.add(aField);
364 }
365 }
366
367 /**
368 * Adds an array of groupby fieldNames for ReportQueries.
369 * @param fieldNames The groupby to set
370 */
371 public void addGroupBy(String[] fieldNames)
372 {
373 for (int i = 0; i < fieldNames.length; i++)
374 {
375 addGroupBy(fieldNames[i]);
376 }
377 }
378
379 /**
380 * @see org.apache.ojb.broker.query.Query#getGroupBy()
381 */
382 public List getGroupBy()
383 {
384 // BRJ:
385 // combine data from query and criteria
386 // TODO: to be removed when Criteria#addGroupBy is removed
387 ArrayList temp = new ArrayList();
388 temp.addAll(m_groupby);
389
390 if (getCriteria() != null)
391 {
392 temp.addAll(getCriteria().getGroupby());
393 }
394
395 return temp;
396 }
397
398 /**
399 * Adds a field for orderBy
400 * @param fieldName The field name to be used
401 * @param sortAscending true for ASCENDING, false for DESCENDING
402 */
403 public void addOrderBy(String fieldName, boolean sortAscending)
404 {
405 if (fieldName != null)
406 {
407 m_orderby.add(new FieldHelper(fieldName, sortAscending));
408 }
409 }
410
411 /**
412 * Adds a field for orderBy, order is ASCENDING
413 * @param fieldName The field name to be used
414 * @deprecated use #addOrderByAscending(String fieldName)
415 */
416 public void addOrderBy(String fieldName)
417 {
418 addOrderBy(fieldName, true);
419 }
420
421 /**
422 * Adds a field for orderBy
423 * @param aField
424 */
425 public void addOrderBy(FieldHelper aField)
426 {
427 if (aField != null)
428 {
429 m_orderby.add(aField);
430 }
431 }
432
433 /**
434 * Adds a field for orderBy ASCENDING
435 * @param fieldName The field name to be used
436 */
437 public void addOrderByAscending(String fieldName)
438 {
439 addOrderBy(fieldName, true);
440 }
441
442 /**
443 * Adds a field for orderBy DESCENDING
444 * @param fieldName The field name to be used
445 */
446 public void addOrderByDescending(String fieldName)
447 {
448 addOrderBy(fieldName, false);
449 }
450
451 /**
452 * @see org.apache.ojb.broker.query.Query#getOrderBy()
453 */
454 public List getOrderBy()
455 {
456 // BRJ:
457 // combine data from query and criteria
458 // TODO: to be removed when Criteria#addOrderBy is removed
459 ArrayList temp = new ArrayList();
460 temp.addAll(m_orderby);
461
462 if (getCriteria() != null)
463 {
464 temp.addAll(getCriteria().getOrderby());
465 }
466
467 return temp;
468 }
469
470 /**
471 * add the name of aRelationship for prefetched read
472 */
473 public void addPrefetchedRelationship(String aName)
474 {
475 m_prefetchedRelationships.add(aName);
476 }
477
478 /* (non-Javadoc)
479 * @see org.apache.ojb.broker.query.Query#getPrefetchedRelationships()
480 */
481 public List getPrefetchedRelationships()
482 {
483 // BRJ:
484 // combine data from query and criteria
485 // TODO: to be removed when Criteria#addPrefetchedRelationship is removed
486 ArrayList temp = new ArrayList();
487 temp.addAll(m_prefetchedRelationships);
488
489 if (getCriteria() != null)
490 {
491 temp.addAll(getCriteria().getPrefetchedRelationships());
492 }
493
494 return temp;
495 }
496
497 /**
498 * Get a Collection containing all Paths having an Outer-Joins-Setting
499 * @return a Collection containing the Paths (Strings)
500 */
501 public Collection getOuterJoinPaths()
502 {
503 return m_pathOuterJoins;
504 }
505
506 public String getObjectProjectionAttribute()
507 {
508 return m_objectProjectionAttribute;
509 }
510
511 /**
512 * Use this method to query some related class by object references,
513 * for example query.setObjectProjectionAttribute("ref1.ref2.ref3");
514 */
515 public void setObjectProjectionAttribute(String objectProjectionAttribute)
516 {
517 ClassDescriptor baseCld = MetadataManager.getInstance().getRepository().getDescriptorFor(m_baseClass);
518 ArrayList descs = baseCld.getAttributeDescriptorsForPath(objectProjectionAttribute);
519 int pathLen = descs.size();
520
521 if ((pathLen > 0) && (descs.get(pathLen - 1) instanceof ObjectReferenceDescriptor))
522 {
523 ObjectReferenceDescriptor ord =
524 ((ObjectReferenceDescriptor) descs.get(pathLen - 1));
525 setObjectProjectionAttribute(objectProjectionAttribute,
526 ord.getItemClass());
527 }
528 }
529
530 public void setObjectProjectionAttribute(String objectProjectionAttribute,
531 Class objectProjectionClass)
532 {
533 m_objectProjectionAttribute = objectProjectionAttribute;
534 m_searchClass = objectProjectionClass;
535 }
536 }