View Javadoc

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 }