001: package org.apache.ojb.broker.query;
002:
003: /* Copyright 2002-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.util.ArrayList;
019: import java.util.Collection;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.List;
023: import java.util.Map;
024:
025: import org.apache.ojb.broker.metadata.ClassDescriptor;
026: import org.apache.ojb.broker.metadata.FieldDescriptor;
027: import org.apache.ojb.broker.metadata.FieldHelper;
028: import org.apache.ojb.broker.metadata.MetadataManager;
029: import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
030: import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
031: import org.apache.ojb.broker.util.logging.LoggerFactory;
032:
033: /**
034: * represents a search by criteria.
035: * "find all articles where article.price > 100"
036: * could be represented as:
037: *
038: * Criteria crit = new Criteria();
039: * crit.addGreaterThan("price", new Double(100));
040: * Query qry = new QueryByCriteria(Article.class, crit);
041: *
042: * The PersistenceBroker can retrieve Objects by Queries as follows:
043: *
044: * PersistenceBroker broker = PersistenceBrokerFactory.createPersistenceBroker();
045: * Collection col = broker.getCollectionByQuery(qry);
046: *
047: * Creation date: (24.01.2001 21:45:46)
048: * @author Thomas Mahler
049: * @version $Id: QueryByCriteria.java,v 1.26.2.4 2005/12/21 22:27:09 tomdz Exp $
050: */
051: public class QueryByCriteria extends AbstractQueryImpl {
052: private Criteria m_criteria;
053: private boolean m_distinct = false;
054: private Map m_pathClasses;
055: private Criteria m_havingCriteria;
056: private String m_objectProjectionAttribute;
057:
058: // holding FieldHelper for orderBy and groupBy
059: private List m_orderby = null;
060: private List m_groupby = null;
061:
062: // list of names of prefetchable relationships
063: private List m_prefetchedRelationships = null;
064:
065: private Collection m_pathOuterJoins = null;
066:
067: /**
068: * handy criteria that can be used to select all instances of
069: * a class.
070: */
071: public static final Criteria CRITERIA_SELECT_ALL = null;
072:
073: /**
074: * Build a Query for class targetClass with criteria.
075: * Criteriy may be null (will result in a query returning ALL objects from a table)
076: */
077: public QueryByCriteria(Class targetClass, Criteria whereCriteria,
078: Criteria havingCriteria, boolean distinct) {
079: super (targetClass);
080:
081: setCriteria(whereCriteria);
082: setHavingCriteria(havingCriteria);
083:
084: m_distinct = distinct;
085: m_pathClasses = new HashMap();
086: m_groupby = new ArrayList();
087: m_orderby = new ArrayList();
088: m_prefetchedRelationships = new ArrayList();
089: m_pathOuterJoins = new HashSet();
090: }
091:
092: /**
093: * Build a Query for class targetClass with criteria.
094: * Criteriy may be null (will result in a query returning ALL objects from a table)
095: */
096: public QueryByCriteria(Class targetClass, Criteria whereCriteria,
097: Criteria havingCriteria) {
098: this (targetClass, whereCriteria, havingCriteria, false);
099: }
100:
101: /**
102: * Build a Query for class targetClass with criteria.
103: * Criteriy may be null (will result in a query returning ALL objects from a table)
104: */
105: public QueryByCriteria(Class targetClass, Criteria criteria) {
106: this (targetClass, criteria, false);
107: }
108:
109: /**
110: * Build a Query for class targetClass with criteria.
111: * Criteriy may be null (will result in a query returning ALL objects from a table)
112: */
113: public QueryByCriteria(Class targetClass, Criteria criteria,
114: boolean distinct) {
115: this (targetClass, criteria, null, distinct);
116: }
117:
118: /**
119: * Build a Query based on anObject <br>
120: * all non null values are used as EqualToCriteria
121: */
122: public QueryByCriteria(Object anObject, boolean distinct) {
123: this (anObject.getClass(), buildCriteria(anObject), distinct);
124: }
125:
126: /**
127: * Build a Query based on anObject <br>
128: * all non null values are used as EqualToCriteria
129: */
130: public QueryByCriteria(Object anObject) {
131: this (anObject.getClass(), buildCriteria(anObject));
132: }
133:
134: /**
135: * Build a Query based on a Class Object. This
136: * Query will return all instances of the given class.
137: * @param aClassToSearchFrom the class to search from
138: */
139: public QueryByCriteria(Class aClassToSearchFrom) {
140: this (aClassToSearchFrom, CRITERIA_SELECT_ALL);
141: }
142:
143: /**
144: * Build Criteria based on example object<br>
145: * all non null values are used as EqualToCriteria
146: */
147: private static Criteria buildCriteria(Object anExample) {
148: Criteria criteria = new Criteria();
149: ClassDescriptor cld = MetadataManager.getInstance()
150: .getRepository().getDescriptorFor(anExample.getClass());
151: FieldDescriptor[] fds = cld.getFieldDescriptions();
152: PersistentField f;
153: Object value;
154:
155: for (int i = 0; i < fds.length; i++) {
156: try {
157: f = fds[i].getPersistentField();
158: value = f.get(anExample);
159: if (value != null) {
160: criteria.addEqualTo(f.getName(), value);
161: }
162: } catch (Throwable ex) {
163: LoggerFactory.getDefaultLogger().error(ex);
164: }
165: }
166:
167: return criteria;
168: }
169:
170: /**
171: * Add a hint Class for a path. Used for relationships to extents.<br>
172: * SqlStatment will use these hint classes when resolving the path.
173: * Without these hints SqlStatment will use the base class the
174: * relationship points to ie: Article instead of CdArticle.
175: *
176: * @param aPath the path segment ie: allArticlesInGroup
177: * @param aClass the Class ie: CdArticle
178: * @see org.apache.ojb.broker.QueryTest#testInversePathExpression()
179: */
180: public void addPathClass(String aPath, Class aClass) {
181: List pathClasses = (List) m_pathClasses.get(aPath);
182: if (pathClasses == null) {
183: setPathClass(aPath, aClass);
184: } else {
185: pathClasses.add(aClass);
186: }
187: }
188:
189: /**
190: * Set the Class for a path. Used for relationships to extents.<br>
191: * SqlStatment will use this class when resolving the path.
192: * Without this hint SqlStatment will use the base class the
193: * relationship points to ie: Article instead of CdArticle.
194: * Using this method is the same as adding just one hint
195: *
196: * @param aPath the path segment ie: allArticlesInGroup
197: * @param aClass the Class ie: CdArticle
198: * @see org.apache.ojb.broker.QueryTest#testInversePathExpression()
199: * @see #addPathClass
200: */
201: public void setPathClass(String aPath, Class aClass) {
202: List pathClasses = new ArrayList();
203: pathClasses.add(aClass);
204: m_pathClasses.put(aPath, pathClasses);
205: }
206:
207: /**
208: * Get the a List of Class objects used as hints for a path
209: *
210: * @param aPath the path segment ie: allArticlesInGroup
211: * @return a List o Class objects to be used in SqlStatment
212: * @see #addPathClass
213: * @see org.apache.ojb.broker.QueryTest#testInversePathExpression()
214: */
215: public List getClassesForPath(String aPath) {
216: return (List) m_pathClasses.get(aPath);
217: }
218:
219: /**
220: * Answer true if outer join for path should be used.
221: * @param aPath the path to query the outer join setting for
222: * @return true for outer join
223: */
224: public boolean isPathOuterJoin(String aPath) {
225: return getOuterJoinPaths().contains(aPath);
226: }
227:
228: /**
229: * Force outer join for the last segment of the path.
230: * ie. path = 'a.b.c' the outer join will be applied only to the relationship from B to C.
231: * if multiple segments need an outer join, setPathOuterJoin needs to be called for each segement.
232: * @param aPath force outer join to the last segment of this path
233: */
234: public void setPathOuterJoin(String aPath) {
235: getOuterJoinPaths().add(aPath);
236: }
237:
238: /* (non-Javadoc)
239: * @see org.apache.ojb.broker.query.Query#getCriteria()
240: */
241: public Criteria getCriteria() {
242: return m_criteria;
243: }
244:
245: /* (non-Javadoc)
246: * @see org.apache.ojb.broker.query.Query#getHavingCriteria()
247: */
248: public Criteria getHavingCriteria() {
249: return m_havingCriteria;
250: }
251:
252: /**
253: * Insert the method's description here.
254: * Creation date: (07.02.2001 22:01:55)
255: * @return java.lang.String
256: */
257: public String toString() {
258: StringBuffer buf = new StringBuffer("QueryByCriteria from ");
259: buf.append(getSearchClass()).append(" ");
260: if (getCriteria() != null && !getCriteria().isEmpty()) {
261: buf.append(" where ").append(getCriteria());
262: }
263: return buf.toString();
264: }
265:
266: /**
267: * Gets the distinct.
268: * @return Returns a boolean
269: */
270: public boolean isDistinct() {
271: return m_distinct;
272: }
273:
274: /**
275: * Sets the distinct.
276: * @param distinct The distinct to set
277: */
278: public void setDistinct(boolean distinct) {
279: this .m_distinct = distinct;
280: }
281:
282: /**
283: * Gets the pathClasses.
284: * A Map containing hints about what Class to be used for what path segment
285: * @return Returns a Map
286: */
287: public Map getPathClasses() {
288: return m_pathClasses;
289: }
290:
291: /**
292: * Sets the criteria.
293: * @param criteria The criteria to set
294: */
295: public void setCriteria(Criteria criteria) {
296: m_criteria = criteria;
297: if (m_criteria != null) {
298: m_criteria.setQuery(this );
299: }
300: }
301:
302: /**
303: * Sets the havingCriteria.
304: * @param havingCriteria The havingCriteria to set
305: */
306: public void setHavingCriteria(Criteria havingCriteria) {
307: m_havingCriteria = havingCriteria;
308: if (m_havingCriteria != null) {
309: m_havingCriteria.setQuery(this );
310: }
311: }
312:
313: /**
314: * Adds a groupby fieldName for ReportQueries.
315: * @param fieldName The groupby to set
316: */
317: public void addGroupBy(String fieldName) {
318: if (fieldName != null) {
319: m_groupby.add(new FieldHelper(fieldName, false));
320: }
321: }
322:
323: /**
324: * Adds a field for groupby
325: * @param aField
326: */
327: public void addGroupBy(FieldHelper aField) {
328: if (aField != null) {
329: m_groupby.add(aField);
330: }
331: }
332:
333: /**
334: * Adds an array of groupby fieldNames for ReportQueries.
335: * @param fieldNames The groupby to set
336: */
337: public void addGroupBy(String[] fieldNames) {
338: for (int i = 0; i < fieldNames.length; i++) {
339: addGroupBy(fieldNames[i]);
340: }
341: }
342:
343: /**
344: * @see org.apache.ojb.broker.query.Query#getGroupBy()
345: */
346: public List getGroupBy() {
347: // BRJ:
348: // combine data from query and criteria
349: // TODO: to be removed when Criteria#addGroupBy is removed
350: ArrayList temp = new ArrayList();
351: temp.addAll(m_groupby);
352:
353: if (getCriteria() != null) {
354: temp.addAll(getCriteria().getGroupby());
355: }
356:
357: return temp;
358: }
359:
360: /**
361: * Adds a field for orderBy
362: * @param fieldName The field name to be used
363: * @param sortAscending true for ASCENDING, false for DESCENDING
364: */
365: public void addOrderBy(String fieldName, boolean sortAscending) {
366: if (fieldName != null) {
367: m_orderby.add(new FieldHelper(fieldName, sortAscending));
368: }
369: }
370:
371: /**
372: * Adds a field for orderBy, order is ASCENDING
373: * @param fieldName The field name to be used
374: * @deprecated use #addOrderByAscending(String fieldName)
375: */
376: public void addOrderBy(String fieldName) {
377: addOrderBy(fieldName, true);
378: }
379:
380: /**
381: * Adds a field for orderBy
382: * @param aField
383: */
384: public void addOrderBy(FieldHelper aField) {
385: if (aField != null) {
386: m_orderby.add(aField);
387: }
388: }
389:
390: /**
391: * Adds a field for orderBy ASCENDING
392: * @param fieldName The field name to be used
393: */
394: public void addOrderByAscending(String fieldName) {
395: addOrderBy(fieldName, true);
396: }
397:
398: /**
399: * Adds a field for orderBy DESCENDING
400: * @param fieldName The field name to be used
401: */
402: public void addOrderByDescending(String fieldName) {
403: addOrderBy(fieldName, false);
404: }
405:
406: /**
407: * @see org.apache.ojb.broker.query.Query#getOrderBy()
408: */
409: public List getOrderBy() {
410: // BRJ:
411: // combine data from query and criteria
412: // TODO: to be removed when Criteria#addOrderBy is removed
413: ArrayList temp = new ArrayList();
414: temp.addAll(m_orderby);
415:
416: if (getCriteria() != null) {
417: temp.addAll(getCriteria().getOrderby());
418: }
419:
420: return temp;
421: }
422:
423: /**
424: * add the name of aRelationship for prefetched read
425: */
426: public void addPrefetchedRelationship(String aName) {
427: m_prefetchedRelationships.add(aName);
428: }
429:
430: /* (non-Javadoc)
431: * @see org.apache.ojb.broker.query.Query#getPrefetchedRelationships()
432: */
433: public List getPrefetchedRelationships() {
434: // BRJ:
435: // combine data from query and criteria
436: // TODO: to be removed when Criteria#addPrefetchedRelationship is removed
437: ArrayList temp = new ArrayList();
438: temp.addAll(m_prefetchedRelationships);
439:
440: if (getCriteria() != null) {
441: temp.addAll(getCriteria().getPrefetchedRelationships());
442: }
443:
444: return temp;
445: }
446:
447: /**
448: * Get a Collection containing all Paths having an Outer-Joins-Setting
449: * @return a Collection containing the Paths (Strings)
450: */
451: public Collection getOuterJoinPaths() {
452: return m_pathOuterJoins;
453: }
454:
455: public String getObjectProjectionAttribute() {
456: return m_objectProjectionAttribute;
457: }
458:
459: /**
460: * Use this method to query some related class by object references,
461: * for example query.setObjectProjectionAttribute("ref1.ref2.ref3");
462: */
463: public void setObjectProjectionAttribute(
464: String objectProjectionAttribute) {
465: ClassDescriptor baseCld = MetadataManager.getInstance()
466: .getRepository().getDescriptorFor(m_baseClass);
467: ArrayList descs = baseCld
468: .getAttributeDescriptorsForPath(objectProjectionAttribute);
469: int pathLen = descs.size();
470:
471: if ((pathLen > 0)
472: && (descs.get(pathLen - 1) instanceof ObjectReferenceDescriptor)) {
473: ObjectReferenceDescriptor ord = ((ObjectReferenceDescriptor) descs
474: .get(pathLen - 1));
475: setObjectProjectionAttribute(objectProjectionAttribute, ord
476: .getItemClass());
477: }
478: }
479:
480: public void setObjectProjectionAttribute(
481: String objectProjectionAttribute,
482: Class objectProjectionClass) {
483: m_objectProjectionAttribute = objectProjectionAttribute;
484: m_searchClass = objectProjectionClass;
485: }
486: }
|