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.1 2007-08-24 22:17:36 ewestfal Exp $
050     */
051    public class QueryByCriteria extends AbstractQueryImpl
052    {
053        private Criteria m_criteria;
054        private boolean m_distinct = false;
055        private Map m_pathClasses;
056        private Criteria m_havingCriteria;
057            private String m_objectProjectionAttribute;
058    
059        // holding FieldHelper for orderBy and groupBy
060        private List m_orderby = null;
061        private List m_groupby = null;
062    
063        // list of names of prefetchable relationships
064        private List m_prefetchedRelationships = null;
065    
066        private Collection m_pathOuterJoins = null;
067    
068        /**
069         * handy criteria that can be used to select all instances of
070         * a class.
071         */
072        public static final Criteria CRITERIA_SELECT_ALL = null;
073    
074        /**
075         * Build a Query for class targetClass with criteria.
076         * Criteriy may be null (will result in a query returning ALL objects from a table)
077         */
078        public QueryByCriteria(Class targetClass, Criteria whereCriteria, Criteria havingCriteria, boolean distinct)
079        {
080            super (targetClass);
081    
082            setCriteria(whereCriteria);
083            setHavingCriteria(havingCriteria);
084    
085            m_distinct = distinct;
086            m_pathClasses = new HashMap();
087            m_groupby = new ArrayList();
088            m_orderby = new ArrayList();
089            m_prefetchedRelationships = new ArrayList();
090            m_pathOuterJoins = new HashSet();
091        }
092    
093        /**
094         * Build a Query for class targetClass with criteria.
095         * Criteriy may be null (will result in a query returning ALL objects from a table)
096         */
097        public QueryByCriteria(Class targetClass, Criteria whereCriteria, Criteria havingCriteria)
098        {
099            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    }