KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > tigris > scarab > util > word > IssueSearch


1 package org.tigris.scarab.util.word;
2
3 /* ================================================================
4  * Copyright (c) 2000-2002 CollabNet. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in the
15  * documentation and/or other materials provided with the distribution.
16  *
17  * 3. The end-user documentation included with the redistribution, if
18  * any, must include the following acknowlegement: "This product includes
19  * software developed by Collab.Net <http://www.Collab.Net/>."
20  * Alternately, this acknowlegement may appear in the software itself, if
21  * and wherever such third-party acknowlegements normally appear.
22  *
23  * 4. The hosted project names must not be used to endorse or promote
24  * products derived from this software without prior written
25  * permission. For written permission, please contact info@collab.net.
26  *
27  * 5. Products derived from this software may not use the "Tigris" or
28  * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without
29  * prior written permission of Collab.Net.
30  *
31  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
32  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
33  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
34  * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
35  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
36  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
37  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
38  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
39  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
40  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42  *
43  * ====================================================================
44  *
45  * This software consists of voluntary contributions made by many
46  * individuals on behalf of Collab.Net.
47  */

48
49 // JDK classes
50
import java.sql.Connection JavaDoc;
51 import java.sql.ResultSet JavaDoc;
52 import java.sql.SQLException JavaDoc;
53 import java.sql.Statement JavaDoc;
54 import java.text.DateFormat JavaDoc;
55 import java.text.ParseException JavaDoc;
56 import java.text.SimpleDateFormat JavaDoc;
57 import java.util.ArrayList JavaDoc;
58 import java.util.Date JavaDoc;
59 import java.util.HashMap JavaDoc;
60 import java.util.HashSet JavaDoc;
61 import java.util.Iterator JavaDoc;
62 import java.util.List JavaDoc;
63 import java.util.Locale JavaDoc;
64 import java.util.Map JavaDoc;
65 import java.util.NoSuchElementException JavaDoc;
66 import java.util.Set JavaDoc;
67
68 import org.apache.commons.collections.map.LRUMap;
69 import org.apache.commons.collections.map.LinkedMap;
70 import org.apache.commons.lang.ObjectUtils;
71 import org.apache.commons.lang.StringUtils;
72 import org.apache.fulcrum.localization.Localization;
73 import org.apache.fulcrum.parser.ParameterParser;
74 import org.apache.fulcrum.parser.StringValueParser;
75 import org.apache.log4j.Logger;
76 import org.apache.torque.Torque;
77 import org.apache.torque.TorqueException;
78 import org.apache.torque.adapter.DB;
79 import org.apache.torque.om.ComboKey;
80 import org.apache.torque.om.ObjectKey;
81 import org.apache.torque.om.SimpleKey;
82 import org.apache.torque.util.Criteria;
83 import org.tigris.scarab.attribute.DateAttribute;
84 import org.tigris.scarab.attribute.OptionAttribute;
85 import org.tigris.scarab.attribute.StringAttribute;
86 import org.tigris.scarab.om.ActivityPeer;
87 import org.tigris.scarab.om.ActivitySetPeer;
88 import org.tigris.scarab.om.AttachmentTypePeer;
89 import org.tigris.scarab.om.Attribute;
90 import org.tigris.scarab.om.AttributeManager;
91 import org.tigris.scarab.om.AttributeValue;
92 import org.tigris.scarab.om.AttributeValuePeer;
93 import org.tigris.scarab.om.Issue;
94 import org.tigris.scarab.om.IssuePeer;
95 import org.tigris.scarab.om.IssueType;
96 import org.tigris.scarab.om.MITList;
97 import org.tigris.scarab.om.MITListItem;
98 import org.tigris.scarab.om.Module;
99 import org.tigris.scarab.om.ModuleManager;
100 import org.tigris.scarab.om.RModuleIssueType;
101 import org.tigris.scarab.om.RModuleIssueTypeManager;
102 import org.tigris.scarab.om.RModuleOption;
103 import org.tigris.scarab.om.RModuleOptionPeer;
104 import org.tigris.scarab.om.RModuleUserAttribute;
105 import org.tigris.scarab.om.ScarabUser;
106 import org.tigris.scarab.services.security.ScarabSecurity;
107 import org.tigris.scarab.tools.ScarabLocalizationTool;
108 import org.tigris.scarab.tools.localization.L10NKeySet;
109 import org.tigris.scarab.util.IteratorWithSize;
110 import org.tigris.scarab.util.Log;
111 import org.tigris.scarab.util.ScarabConstants;
112 import org.tigris.scarab.util.ScarabException;
113
114 /**
115  * A utility class to build up and carry out a search for
116  * similar issues. It subclasses Issue for functionality, it is
117  * not a more specific type of Issue.
118  *
119  * @author <a HREF="mailto:jmcnally@collab.net">John McNally</a>
120  * @version $Id: IssueSearch.java 9746 2005-06-22 06:20:23Z jorgeuriarte $
121  */

122 public class IssueSearch
123     extends Issue
124 {
125     private static final int MAX_INNER_JOIN =
126         ScarabConstants.QUERY_MAX_FILTER_CRITERIA;
127
128     private static final int MAX_JOIN =
129         ScarabConstants.QUERY_MAX_JOIN;
130
131     public static final String JavaDoc ASC = "asc";
132     public static final String JavaDoc DESC = "desc";
133
134     public static final String JavaDoc CREATED_BY_KEY = "created_by";
135     public static final String JavaDoc ANY_KEY = "any";
136
137     // column names only
138
private static final String JavaDoc AV_OPTION_ID =
139         AttributeValuePeer.OPTION_ID.substring(
140         AttributeValuePeer.OPTION_ID.indexOf('.')+1);
141     private static final String JavaDoc AV_ISSUE_ID =
142         AttributeValuePeer.ISSUE_ID.substring(
143         AttributeValuePeer.ISSUE_ID.indexOf('.')+1);
144     private static final String JavaDoc AV_USER_ID =
145         AttributeValuePeer.USER_ID.substring(
146         AttributeValuePeer.USER_ID.indexOf('.')+1);
147
148     private static final String JavaDoc ACTIVITYSETALIAS = "srchcobyactset";
149     private static final String JavaDoc USERAVALIAS = "srchuav";
150     private static final String JavaDoc ACTIVITYALIAS = "srchcobyact";
151
152     private static final String JavaDoc CREATED_BY = "CREATED_BY";
153     private static final String JavaDoc CREATED_DATE = "CREATED_DATE";
154     private static final String JavaDoc ATTRIBUTE_ID = "ATTRIBUTE_ID";
155     private static final String JavaDoc AND = " AND ";
156     private static final String JavaDoc OR = " OR ";
157     private static final String JavaDoc INNER_JOIN = " INNER JOIN ";
158     private static final String JavaDoc ON = " ON (";
159     private static final String JavaDoc IN = " IN (";
160     private static final String JavaDoc IS_NULL = " IS NULL";
161     private static final String JavaDoc LEFT_OUTER_JOIN = " LEFT OUTER JOIN ";
162     private static final String JavaDoc SELECT_DISTINCT = "select DISTINCT ";
163     
164     private static final String JavaDoc ACT_TRAN_ID =
165         ActivityPeer.TRANSACTION_ID.substring(
166         ActivityPeer.TRANSACTION_ID.indexOf('.')+1);
167     private static final String JavaDoc ACTSET_TRAN_ID =
168         ActivitySetPeer.TRANSACTION_ID.substring(
169         ActivitySetPeer.TRANSACTION_ID.indexOf('.')+1);
170     private static final String JavaDoc
171         ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID =
172         IssuePeer.CREATED_TRANS_ID + '=' +
173         ACTIVITYSETALIAS + '.' + ACTSET_TRAN_ID;
174
175     private static final String JavaDoc ACT_ISSUE_ID =
176         ActivityPeer.ISSUE_ID.substring(ActivityPeer.ISSUE_ID.indexOf('.')+1);
177     private static final String JavaDoc ACTIVITYALIAS_ISSUE_ID =
178         ACTIVITYALIAS + '.' + ACT_ISSUE_ID;
179     private static final String JavaDoc
180         ACTIVITYALIAS_ISSUE_ID__EQUALS__ISSUEPEER_ISSUE_ID =
181         ACTIVITYALIAS_ISSUE_ID + '=' + IssuePeer.ISSUE_ID;
182     private static final String JavaDoc END_DATE =
183         ActivityPeer.END_DATE.substring(
184         ActivityPeer.END_DATE.indexOf('.')+1);
185
186     private static final String JavaDoc ACT_ATTR_ID =
187         ActivityPeer.ATTRIBUTE_ID.substring(
188         ActivityPeer.ATTRIBUTE_ID.indexOf('.')+1);
189     private static final String JavaDoc AV_ATTR_ID =
190         AttributeValuePeer.ATTRIBUTE_ID.substring(
191         AttributeValuePeer.ATTRIBUTE_ID.indexOf('.')+1);
192     private static final String JavaDoc ACTIVITYALIAS_ATTRIBUTE_ID =
193         ACTIVITYALIAS + '.' + ACT_ATTR_ID;
194
195
196     private static final String JavaDoc USERAVALIAS_ISSUE_ID =
197         USERAVALIAS + '.' + AV_ISSUE_ID;
198
199     private static final String JavaDoc ACT_NEW_USER_ID =
200         ActivityPeer.NEW_USER_ID.substring(
201         ActivityPeer.NEW_USER_ID.indexOf('.')+1);
202     private static final String JavaDoc ACTIVITYALIAS_NEW_USER_ID =
203         ACTIVITYALIAS + '.' + ACT_NEW_USER_ID;
204
205     private static final String JavaDoc WHERE = " WHERE ";
206     private static final String JavaDoc FROM = " FROM ";
207     private static final String JavaDoc ORDER_BY = " ORDER BY ";
208     private static final String JavaDoc BASE_OPTION_SORT_LEFT_JOIN =
209         " LEFT OUTER JOIN " + RModuleOptionPeer.TABLE_NAME + " sortRMO ON " +
210         '(' + IssuePeer.MODULE_ID + "=sortRMO.MODULE_ID AND " +
211         IssuePeer.TYPE_ID + "=sortRMO.ISSUE_TYPE_ID AND sortRMO.OPTION_ID=";
212     private static final String JavaDoc AV = "av";
213     private static final String JavaDoc DOT_OPTION_ID_PAREN = ".OPTION_ID)";
214     private static final String JavaDoc DOT_VALUE = ".VALUE";
215     private static final String JavaDoc SORTRMO_PREFERRED_ORDER =
216         "sortRMO.PREFERRED_ORDER";
217
218
219     private static final Integer JavaDoc NUMBERKEY_0 = new Integer JavaDoc(0);
220
221     /**
222      * The managed database connection used while iterating over large
223      * query result sets using a cursor. This connection <b>must</b>
224      * be explicitly closed when done with it (e.g. at the end of the
225      * request)!
226      */

227     private Connection JavaDoc conn;
228
229     /**
230      * The statement that will be used by the connection to get the issue
231      * ids for the query.
232      */

233     private Statement JavaDoc searchStmt;
234     /**
235      * The ResultSet(s) that contains the issue ids for a query.
236      */

237     private ResultSet JavaDoc searchRS;
238
239     /**
240      * The statement(s) that will be used by the connection to obtain the
241      * ResultSet for column data to be shown for the query. Statements should
242      * be closed when no longer needed. Closing a Statement will release any
243      * associated ResultSets for a compliant jdbc driver, but we don't rely
244      * on this behavior.
245      */

246     private List JavaDoc stmtList;
247     /**
248      * The ResultSet(s) that contain query result column data. We store the
249      * size along with the RS in a ResultSetAndSize object, where size is
250      * the number of columns in the RS.
251      */

252     private List JavaDoc rsList;
253
254     /**
255      * used to track how long we hold the connection
256      */

257     private long connectionStartTime;
258
259     private SimpleDateFormat JavaDoc formatter;
260
261     private String JavaDoc searchWords;
262     private String JavaDoc commentQuery;
263     private Integer JavaDoc[] textScope;
264     private String JavaDoc minId;
265     private String JavaDoc maxId;
266     private String JavaDoc minDate;
267     private String JavaDoc maxDate;
268     private int minVotes;
269     
270     private Integer JavaDoc stateChangeAttributeId;
271     private Integer JavaDoc stateChangeFromOptionId;
272     private Integer JavaDoc stateChangeToOptionId;
273     private String JavaDoc stateChangeFromDate;
274     private String JavaDoc stateChangeToDate;
275
276     private Integer JavaDoc sortAttributeId;
277     private String JavaDoc sortPolarity;
278     private MITList mitList;
279
280     private List JavaDoc userIdList;
281     private List JavaDoc userSearchCriteriaList;
282     private List JavaDoc lastUsedAVList;
283     private boolean modified;
284
285     private int lastTotalIssueCount = -1;
286     private List JavaDoc lastMatchingIssueIds = null;
287     private IteratorWithSize lastQueryResults = null;
288
289     // the attribute columns that will be shown
290
private List JavaDoc issueListAttributeColumns;
291
292     // used to cache a few modules and issuetypes to make listing
293
// a result set faster.
294
private LRUMap moduleMap = new LRUMap(20);
295     private LRUMap rmitMap = new LRUMap(20);
296
297     private boolean isSearchAllowed = true;
298
299     /** A counter of inner joins used in a query */
300     private int joinCounter;
301     
302     /**
303      * This is the locale that the search is currently running
304      * under. We need it to parse the date attributes. It defaults
305      * to the US locale as that was the behaviour before.
306      * @todo Ideally, the minDate, maxDate and others should
307      * be Date objects, with the user of this class doing the
308      * parsing itself. However, the intake tool is currently
309      * configured to use this class directly. Hopefully when
310      * (if?) intake supports dates natively, we can drop the
311      * date parsing from this class and use Dates instead.
312      */

313     private Locale JavaDoc locale = Locale.US;
314     
315     private StringValueParser parser = null;
316     
317     private ScarabLocalizationTool L10N = null;
318
319     IssueSearch(Issue issue, ScarabUser searcher)
320         throws Exception JavaDoc
321     {
322         this(issue.getModule(), issue.getIssueType(), searcher);
323         
324         //
325
// Make copies of the issue's attribute values so that
326
// we can modify them later without affecting the issue
327
// itself.
328
//
329
// @todo: This section of code is a result of SCB965.
330
// However, I think a more significant problem is that
331
// ReportIssue is modifying the search's attribute values
332
// directly. I believe this breaks some OO principle or
333
// other and should be resolved some time.
334
//
335
List JavaDoc issueAttributes = issue.getAttributeValues();
336         List JavaDoc searchAttributes = this.getAttributeValues();
337         
338         for (Iterator JavaDoc iter = issueAttributes.iterator(); iter.hasNext(); ) {
339             AttributeValue value = (AttributeValue) iter.next();
340             searchAttributes.add(value.copy());
341         }
342     }
343
344     IssueSearch(Module module, IssueType issueType, ScarabUser searcher)
345         throws Exception JavaDoc
346     {
347         super(module, issueType);
348         isSearchAllowed =
349             searcher.hasPermission(ScarabSecurity.ISSUE__SEARCH, module);
350     }
351
352     IssueSearch(MITList mitList, ScarabUser searcher)
353         throws Exception JavaDoc
354     {
355         super();
356         if (mitList == null || mitList.size() == 0)
357         {
358             throw new IllegalArgumentException JavaDoc("A non-null list with at" +
359                " least one item is required."); //EXCEPTION
360
}
361
362         String JavaDoc[] perms = {ScarabSecurity.ISSUE__SEARCH};
363         MITList searchableList = mitList
364             .getPermittedSublist(perms, searcher);
365
366         isSearchAllowed = searchableList.size() > 0;
367         isSearchAllowed=true;
368
369         if (searchableList.isSingleModuleIssueType())
370         {
371             MITListItem item = searchableList.getFirstItem();
372             setModuleId(item.getModuleId());
373             setTypeId(item.getIssueTypeId());
374         }
375         else
376         {
377             this.mitList = searchableList;
378             if (searchableList.isSingleModule())
379             {
380                 setModule(searchableList.getModule());
381             }
382             if (searchableList.isSingleIssueType())
383             {
384                 setIssueType(searchableList.getIssueType());
385             }
386         }
387     }
388
389     public Locale JavaDoc getLocale() {
390         return this.locale;
391     }
392     
393     public void setLocale(Locale JavaDoc newLocale) {
394         this.locale = newLocale;
395     }
396
397     public boolean isXMITSearch()
398     {
399         return mitList != null && !mitList.isSingleModuleIssueType();
400     }
401
402     /**
403      * List of attributes to show with each issue.
404      *
405      * @param rmuas a <code>List</code> of RModuleUserAttribute objects
406      */

407     public void setIssueListAttributeColumns(List JavaDoc rmuas)
408     {
409         //FIXME! implement logic to determine if a new search is required.
410
//HELP: John, would it be sufficient to set modified=true?
411
issueListAttributeColumns = rmuas;
412     }
413
414     public List JavaDoc getIssueListAttributeColumns()
415     {
416         return issueListAttributeColumns;
417     }
418
419     public List JavaDoc getUserIdList()
420     {
421         return userIdList;
422     }
423
424     public LinkedMap getCommonAttributeValuesMap()
425         throws Exception JavaDoc
426     {
427         LinkedMap result = null;
428         if (isXMITSearch())
429         {
430             result = getMITAttributeValuesMap();
431         }
432         else
433         {
434             result = super.getModuleAttributeValuesMap(false);
435         }
436         return result;
437     }
438
439     /**
440      * AttributeValues that are relevant to the issue's current module.
441      * Empty AttributeValues that are relevant for the module, but have
442      * not been set for the issue are included. The values are ordered
443      * according to the module's preference
444      */

445     private LinkedMap getMITAttributeValuesMap()
446         throws Exception JavaDoc
447     {
448         LinkedMap result = null;
449
450         List JavaDoc attributes = mitList.getCommonAttributes(false);
451         Map JavaDoc siaValuesMap = getAttributeValuesMap();
452         if (attributes != null)
453         {
454             result = new LinkedMap((int)(1.25*attributes.size() + 1));
455             Iterator JavaDoc i = attributes.iterator();
456             while (i.hasNext())
457             {
458                 Attribute attribute = (Attribute)i.next();
459                 String JavaDoc key = attribute.getName().toUpperCase();
460                 if (siaValuesMap.containsKey(key))
461                 {
462                     result.put(key, siaValuesMap.get(key));
463                 }
464                 else
465                 {
466                     AttributeValue aval = AttributeValue
467                         .getNewInstance(attribute, this);
468                     addAttributeValue(aval);
469                     result.put(key, aval);
470                 }
471             }
472         }
473         return result;
474     }
475
476     /**
477      * @return The list of attributes of type "user" for the module(s)
478      * to search in.
479      */

480     public List JavaDoc getUserAttributes()
481         throws Exception JavaDoc
482     {
483         List JavaDoc result = null;
484         if (isXMITSearch())
485         {
486             result = mitList.getCommonUserAttributes(false);
487         }
488         else
489         {
490             result = getModule().getUserAttributes(getIssueType(), false);
491         }
492         return result;
493     }
494
495     public List JavaDoc getLeafRModuleOptions(Attribute attribute)
496         throws Exception JavaDoc
497     {
498         List JavaDoc result = null;
499         if (isXMITSearch())
500         {
501             result = mitList.getCommonLeafRModuleOptions(attribute);
502         }
503         else
504         {
505             result = getModule()
506                 .getLeafRModuleOptions(attribute, getIssueType());
507         }
508         return result;
509     }
510
511     public List JavaDoc getCommonOptionTree(Attribute attribute)
512         throws Exception JavaDoc
513     {
514         return mitList.getCommonRModuleOptionTree(attribute);
515     }
516
517     public List JavaDoc getAllOptionTree(Attribute attribute)
518         throws Exception JavaDoc
519     {
520         return mitList.getAllRModuleOptionTree(attribute);
521     }
522
523     /**
524      * Get the words for which to search.
525      *
526      * @return Value of {@link #searchWords}.
527      */

528     public String JavaDoc getSearchWords()
529     {
530         return searchWords;
531     }
532     
533     /**
534      * Set the words for which to search.
535      *
536      * @param v Value to assign to {@link #searchWords}.
537      */

538     public void setSearchWords(String JavaDoc v)
539     {
540         if (!ObjectUtils.equals(v, this.searchWords))
541         {
542             modified = true;
543             this.searchWords = v;
544         }
545     }
546
547     
548     /**
549      * Get the value of commentQuery.
550      * @return value of commentQuery.
551      */

552     public String JavaDoc getCommentQuery()
553     {
554         return commentQuery;
555     }
556     
557     /**
558      * Set the value of commentQuery.
559      * @param v Value to assign to commentQuery.
560      */

561     public void setCommentQuery(String JavaDoc v)
562     {
563         if (!ObjectUtils.equals(v, this.commentQuery))
564         {
565             modified = true;
566             this.commentQuery = v;
567         }
568     }
569     
570     /**
571      * Get the value of textScope. if the scope is not set then all
572      * text attributes are returned. if there are no relevant text
573      * attributes null will be returned.
574      * @return value of textScope.
575      */

576     public Integer JavaDoc[] getTextScope()
577         throws Exception JavaDoc
578     {
579         if (textScope == null)
580         {
581             textScope = getTextScopeForAll();
582         }
583         else
584         {
585             for (int i = textScope.length - 1; i >= 0; i--)
586             {
587                 if (NUMBERKEY_0.equals(textScope[i]))
588                 {
589                     textScope = getTextScopeForAll();
590                     break;
591                 }
592             }
593         }
594         return textScope;
595     }
596
597
598     /**
599      * Sets the text search scope to all quick search text attributes.
600      */

601     private Integer JavaDoc[] getTextScopeForAll()
602         throws Exception JavaDoc
603     {
604         Integer JavaDoc[] textScope = null;
605         List JavaDoc textAttributes = getQuickSearchTextAttributeValues();
606         if (textAttributes != null)
607         {
608             textScope = new Integer JavaDoc[textAttributes.size()];
609             for (int j=textAttributes.size()-1; j>=0; j--)
610             {
611                 textScope[j] = ((AttributeValue)
612                                 textAttributes.get(j)).getAttributeId();
613             }
614         }
615         return textScope;
616     }
617
618     /**
619      * Set the value of textScope.
620      * @param v Value to assign to textScope.
621      */

622     public void setTextScope(Integer JavaDoc[] v)
623         throws Exception JavaDoc
624     {
625         if (v != null)
626         {
627             for (int i=v.length-1; i>=0; i--)
628             {
629                 if (v[i].equals(NUMBERKEY_0))
630                 {
631                     v = getTextScopeForAll();
632                     break;
633                 }
634             }
635         }
636
637         // note previous block may have made v == null though its not likely
638
// (don't replace the if with an else)
639
if (v == null)
640         {
641             modified |= this.textScope != null;
642             this.textScope = null;
643         }
644         else if (this.textScope != null && this.textScope.length == v.length)
645         {
646             for (int i=v.length-1; i>=0; i--)
647             {
648                 if (!v[i].equals(this.textScope[i]))
649                 {
650                     modified = true;
651                     this.textScope = v;
652                     break;
653                 }
654             }
655         }
656         else
657         {
658             modified = true;
659             this.textScope = v;
660         }
661     }
662
663
664     /**
665      * Get the value of minId.
666      * @return value of minId.
667      */

668     public String JavaDoc getMinId()
669     {
670         return minId;
671     }
672     
673     /**
674      * Set the value of minId.
675      * @param v Value to assign to minId.
676      */

677     public void setMinId(String JavaDoc v)
678     {
679         if (v != null && v.length() == 0)
680         {
681             v = null;
682         }
683         if (!ObjectUtils.equals(v, this.minId))
684         {
685             modified = true;
686             this.minId = v;
687         }
688     }
689
690     
691     /**
692      * Get the value of maxId.
693      * @return value of maxId.
694      */

695     public String JavaDoc getMaxId()
696     {
697         return maxId;
698     }
699     
700     /**
701      * Set the value of maxId.
702      * @param v Value to assign to maxId.
703      */

704     public void setMaxId(String JavaDoc v)
705     {
706         if (v != null && v.length() == 0)
707         {
708             v = null;
709         }
710         if (!ObjectUtils.equals(v, this.maxId))
711         {
712             modified = true;
713             this.maxId = v;
714         }
715     }
716     
717     
718     /**
719      * Get the value of minDate.
720      * @return value of minDate.
721      */

722     public String JavaDoc getMinDate()
723     {
724         return this.minDate;
725     }
726     
727     /**
728      * Set the value of minDate.
729      * @param newMinDate Value to assign to minDate.
730      */

731     public void setMinDate(String JavaDoc newMinDate)
732     {
733         if (newMinDate != null && newMinDate.length() == 0)
734         {
735             newMinDate = null;
736         }
737         
738         if (!ObjectUtils.equals(newMinDate, this.minDate))
739         {
740             this.modified = true;
741             this.minDate = newMinDate;
742         }
743     }
744
745     
746     /**
747      * Get the value of maxDate.
748      * @return value of maxDate.
749      */

750     public String JavaDoc getMaxDate()
751     {
752         return this.maxDate;
753     }
754     
755     /**
756      * Set the value of maxDate.
757      * @param newMaxDate Value to assign to maxDate.
758      */

759     public void setMaxDate(String JavaDoc newMaxDate)
760     {
761         if (newMaxDate != null && newMaxDate.length() == 0)
762         {
763             newMaxDate = null;
764         }
765         
766         if (!ObjectUtils.equals(newMaxDate, this.maxDate))
767         {
768             this.modified = true;
769             this.maxDate = newMaxDate;
770         }
771     }
772     
773     /**
774      * Get the value of minVotes.
775      * @return value of minVotes.
776      */

777     public int getMinVotes()
778     {
779         return minVotes;
780     }
781     
782     /**
783      * Set the value of minVotes.
784      * @param v Value to assign to minVotes.
785      */

786     public void setMinVotes(int v)
787     {
788         if (v != this.minVotes)
789         {
790             modified = true;
791             this.minVotes = v;
792         }
793     }
794
795
796     /**
797      * Get the value of stateChangeAttributeId.
798      * @return value of stateChangeAttributeId.
799      */

800     public Integer JavaDoc getStateChangeAttributeId()
801     {
802         return stateChangeAttributeId;
803     }
804     
805     /**
806      * Set the value of stateChangeAttributeId.
807      * @param v Value to assign to stateChangeAttributeId.
808      */

809     public void setStateChangeAttributeId(Integer JavaDoc v)
810     {
811         if (!ObjectUtils.equals(v, this.stateChangeAttributeId))
812         {
813             modified = true;
814             this.stateChangeAttributeId = v;
815         }
816     }
817         
818     /**
819      * Get the value of stateChangeFromOptionId.
820      * @return value of stateChangeFromOptionId.
821      */

822     public Integer JavaDoc getStateChangeFromOptionId()
823     {
824         return stateChangeFromOptionId;
825     }
826     
827     /**
828      * Set the value of stateChangeFromOptionId.
829      * @param v Value to assign to stateChangeFromOptionId.
830      */

831     public void setStateChangeFromOptionId(Integer JavaDoc v)
832     {
833         if (!ObjectUtils.equals(v, this.stateChangeFromOptionId))
834         {
835             modified = true;
836             this.stateChangeFromOptionId = v;
837         }
838     }
839     
840     /**
841      * Get the value of stateChangeToOptionId.
842      * @return value of stateChangeToOptionId.
843      */

844     public Integer JavaDoc getStateChangeToOptionId()
845     {
846         return stateChangeToOptionId;
847     }
848     
849     /**
850      * Set the value of stateChangeToOptionId.
851      * @param v Value to assign to stateChangeToOptionId.
852      */

853     public void setStateChangeToOptionId(Integer JavaDoc v)
854     {
855         if (!ObjectUtils.equals(v, this.stateChangeToOptionId))
856         {
857             modified = true;
858             this.stateChangeToOptionId = v;
859         }
860     }
861
862     
863     /**
864      * Get the value of stateChangeFromDate.
865      * @return value of stateChangeFromDate.
866      */

867     public String JavaDoc getStateChangeFromDate()
868     {
869         return this.stateChangeFromDate;
870     }
871     
872     /**
873      * Set the value of stateChangeFromDate.
874      * @param fromDate Value to assign to stateChangeFromDate.
875      */

876     public void setStateChangeFromDate(String JavaDoc fromDate)
877     {
878         if (fromDate != null && fromDate.length() == 0)
879         {
880             fromDate = null;
881         }
882         
883         if (!ObjectUtils.equals(fromDate, this.stateChangeFromDate))
884         {
885             this.modified = true;
886             this.stateChangeFromDate = fromDate;
887         }
888     }
889     
890     
891     /**
892      * Get the value of stateChangeToDate.
893      * @return value of stateChangeToDate.
894      */

895     public String JavaDoc getStateChangeToDate()
896     {
897         return this.stateChangeToDate;
898     }
899     
900     /**
901      * Set the value of stateChangeToDate.
902      * @param toDate Value to assign to stateChangeToDate.
903      */

904     public void setStateChangeToDate(String JavaDoc toDate)
905     {
906         if (toDate != null && toDate.length() == 0)
907         {
908             toDate = null;
909         }
910         
911         if (!ObjectUtils.equals(toDate, this.stateChangeToDate))
912         {
913             this.modified = true;
914             this.stateChangeToDate = toDate;
915         }
916     }
917     
918     
919     /**
920      * Get the value of sortAttributeId.
921      * @return value of SortAttributeId.
922      */

923     public Integer JavaDoc getSortAttributeId()
924     {
925         return sortAttributeId;
926     }
927     
928     /**
929      * Set the value of sortAttributeId.
930      * @param v Value to assign to sortAttributeId.
931      */

932     public void setSortAttributeId(Integer JavaDoc v)
933     {
934         if (!ObjectUtils.equals(v, this.sortAttributeId))
935         {
936             modified = true;
937             this.sortAttributeId = v;
938         }
939     }
940
941     /**
942      * Whether to do SQL sorting in <code>DESC</code> or
943      * <code>ASC</code> order (the default being the latter).
944      * @return value of sortPolarity.
945      */

946     public String JavaDoc getSortPolarity()
947     {
948         return (DESC.equals(sortPolarity) ? DESC : ASC);
949     }
950     
951     /**
952      * Set the value of sortPolarity.
953      * @param v Value to assign to sortPolarity.
954      */

955     public void setSortPolarity(String JavaDoc v)
956     {
957         if (!ObjectUtils.equals(v, this.sortPolarity))
958         {
959             modified = true;
960             this.sortPolarity = v;
961         }
962     }
963
964     /**
965      * Describe <code>addUserSearch</code> method here.
966      *
967      * @param userId a <code>String</code> represention of the PrimaryKey
968      * @param searchCriteria a <code>String</code> either a String
969      * representation of an Attribute PrimaryKey, or the Strings "created_by"
970      * "any"
971      */

972     public void addUserCriteria(String JavaDoc userId, String JavaDoc searchCriteria)
973     {
974         if (userId == null)
975         {
976             throw new IllegalArgumentException JavaDoc("userId cannot be null."); //EXCEPTION
977
}
978         if (searchCriteria == null)
979         {
980             searchCriteria = ANY_KEY;
981         }
982
983         if (userIdList == null)
984         {
985             userIdList = new ArrayList JavaDoc(4);
986             userSearchCriteriaList = new ArrayList JavaDoc(4);
987         }
988         boolean newCriteria = true;
989         for (int i=userIdList.size()-1; i>=0 && newCriteria; i--)
990         {
991             Object JavaDoc attrId = userSearchCriteriaList.get(i);
992             // not new if attrId already present or an ANY search has already
993
// been specified
994
newCriteria = !(userId.equals(userIdList.get(i)) &&
995                (searchCriteria.equals(attrId) || ANY_KEY.equals(attrId)));
996         }
997         
998         if (newCriteria)
999         {
1000            modified = true;
1001            // if the new criteria is ANY, then remove old criteria
1002
if (ANY_KEY.equals(searchCriteria))
1003            {
1004                for (int i=userIdList.size()-1; i>=0; i--)
1005                {
1006                    if (userId.equals(userIdList.get(i)))
1007                    {
1008                        userIdList.remove(i);
1009                        userSearchCriteriaList.remove(i);
1010                    }
1011                }
1012            }
1013            userIdList.add(userId);
1014            userSearchCriteriaList.add(searchCriteria);
1015        }
1016    }
1017
1018    private boolean isAVListModified()
1019        throws TorqueException
1020    {
1021        boolean result = false;
1022        if (lastUsedAVList == null)
1023        {
1024            result = true;
1025        }
1026        else
1027        {
1028            List JavaDoc avList = getAttributeValues();
1029            int max = avList.size();
1030            if (lastUsedAVList.size() == max)
1031            {
1032                for (int i=0; i<max; i++)
1033                {
1034                    AttributeValue a1 = (AttributeValue)avList.get(i);
1035                    AttributeValue a2 = (AttributeValue)lastUsedAVList.get(i);
1036                    if (!ObjectUtils.equals(a1.getOptionId(), a2.getOptionId())
1037                         || !ObjectUtils.equals(a1.getUserId(), a2.getUserId())
1038                         //|| a1.getNumericValue() != a2.getNumericValue()
1039
|| !ObjectUtils.equals(a1.getValue(), a2.getValue()))
1040                    {
1041                        result = true;
1042                    }
1043                }
1044            }
1045            else
1046            {
1047                result = true;
1048            }
1049        }
1050        return result;
1051    }
1052
1053    /**
1054     *
1055     *
1056     * @return a <code>boolean</code> value
1057     */

1058    private void checkModified()
1059        throws TorqueException
1060    {
1061        if (modified || isAVListModified())
1062        {
1063            modified = false;
1064            lastTotalIssueCount = -1;
1065            lastMatchingIssueIds = null;
1066            lastQueryResults = null;
1067        }
1068    }
1069
1070    public Integer JavaDoc getALL_TEXT()
1071    {
1072        return NUMBERKEY_0;
1073    }
1074
1075    public List JavaDoc getQuickSearchTextAttributeValues()
1076        throws Exception JavaDoc
1077    {
1078        return getTextAttributeValues(true);
1079    }
1080
1081    public List JavaDoc getTextAttributeValues()
1082        throws Exception JavaDoc
1083    {
1084        return getTextAttributeValues(false);
1085    }
1086
1087    private List JavaDoc getTextAttributeValues(boolean quickSearchOnly)
1088        throws Exception JavaDoc
1089    {
1090        LinkedMap searchValues = getCommonAttributeValuesMap();
1091        List JavaDoc searchAttributes = new ArrayList JavaDoc(searchValues.size());
1092
1093        for (int i=0; i<searchValues.size(); i++)
1094        {
1095            AttributeValue searchValue =
1096                (AttributeValue)searchValues.getValue(i);
1097            if ((!quickSearchOnly || searchValue.isQuickSearchAttribute())
1098                 && searchValue.getAttribute().isTextAttribute())
1099            {
1100                searchAttributes.add(searchValue);
1101            }
1102        }
1103
1104        return searchAttributes;
1105    }
1106
1107    /**
1108     * Returns OptionAttributes which have been marked for Quick search.
1109     *
1110     * @return a <code>List</code> value
1111     * @exception Exception if an error occurs
1112     */

1113    public List JavaDoc getQuickSearchOptionAttributeValues()
1114        throws Exception JavaDoc
1115    {
1116        return getOptionAttributeValues(true);
1117    }
1118
1119    /**
1120     * Returns OptionAttributes which have been marked for Quick search.
1121     *
1122     * @return a <code>List</code> value
1123     * @exception Exception if an error occurs
1124     */

1125    public List JavaDoc getOptionAttributeValues()
1126        throws Exception JavaDoc
1127    {
1128        return getOptionAttributeValues(false);
1129    }
1130
1131
1132    /**
1133     * Returns OptionAttributes which have been marked for Quick search.
1134     *
1135     * @return a <code>List</code> value
1136     * @exception Exception if an error occurs
1137     */

1138    private List JavaDoc getOptionAttributeValues(boolean quickSearchOnly)
1139        throws Exception JavaDoc
1140    {
1141        LinkedMap searchValues = getCommonAttributeValuesMap();
1142        List JavaDoc searchAttributeValues = new ArrayList JavaDoc(searchValues.size());
1143
1144        for (int i=0; i<searchValues.size(); i++)
1145        {
1146            AttributeValue searchValue =
1147                (AttributeValue)searchValues.getValue(i);
1148            if ((!quickSearchOnly || searchValue.isQuickSearchAttribute())
1149                 && searchValue instanceof OptionAttribute)
1150            {
1151                searchAttributeValues.add(searchValue);
1152            }
1153        }
1154
1155        return searchAttributeValues;
1156    }
1157
1158
1159    /**
1160     * remove unset AttributeValues.
1161     *
1162     * @param attValues a <code>List</code> value
1163     */

1164    private List JavaDoc removeUnsetValues(List JavaDoc attValues)
1165    {
1166        int size = attValues.size();
1167        List JavaDoc setAVs = new ArrayList JavaDoc(size);
1168        for (int i=0; i<size; i++)
1169        {
1170            AttributeValue attVal = (AttributeValue) attValues.get(i);
1171            if (attVal.isSet())
1172            {
1173                setAVs.add(attVal);
1174            }
1175        }
1176        return setAVs;
1177    }
1178
1179
1180    private void addAnd(StringBuffer JavaDoc sb)
1181    {
1182        if (sb.length() > 0)
1183        {
1184            sb.append(AND);
1185        }
1186    }
1187
1188    private void addIssueIdRange(StringBuffer JavaDoc where)
1189        throws ScarabException, Exception JavaDoc
1190    {
1191        // check limits to see which ones are present
1192
// if neither are present, do nothing
1193
if ((minId != null && minId.length() != 0)
1194              || (maxId != null && maxId.length() != 0))
1195        {
1196            StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1197            String JavaDoc domain = null;
1198            String JavaDoc prefix = null;
1199            Issue.FederatedId minFid = null;
1200            Issue.FederatedId maxFid = null;
1201            if (minId == null || minId.length() == 0)
1202            {
1203                maxFid = new Issue.FederatedId(maxId);
1204                setDefaults(null, maxFid);
1205                addAnd(sb);
1206                sb.append(IssuePeer.ID_COUNT).append("<=")
1207                    .append(maxFid.getCount());
1208                domain = maxFid.getDomain();
1209                prefix = maxFid.getPrefix();
1210            }
1211            else if (maxId == null || maxId.length() == 0)
1212            {
1213                minFid = new Issue.FederatedId(minId);
1214                setDefaults(minFid, null);
1215                addAnd(sb);
1216                sb.append(IssuePeer.ID_COUNT).append(">=")
1217                    .append(minFid.getCount());
1218                domain = minFid.getDomain();
1219                prefix = minFid.getPrefix();
1220            }
1221            else
1222            {
1223                minFid = new Issue.FederatedId(minId);
1224                maxFid = new Issue.FederatedId(maxId);
1225                setDefaults(minFid, maxFid);
1226                
1227                // make sure min id is less than max id and that the character
1228
// parts are equal otherwise skip the query, there are no
1229
// matches
1230
if (minFid.getCount() <= maxFid.getCount()
1231                  && StringUtils.equals(minFid.getPrefix(), maxFid.getPrefix())
1232                  && StringUtils.equals(minFid.getDomain(), maxFid.getDomain()))
1233                {
1234                    addAnd(sb);
1235                    sb.append(IssuePeer.ID_COUNT).append(">=")
1236                        .append(minFid.getCount()).append(AND)
1237                        .append(IssuePeer.ID_COUNT).append("<=")
1238                        .append(maxFid.getCount());
1239                    domain = minFid.getDomain();
1240                    prefix = minFid.getPrefix();
1241                }
1242                else
1243                {
1244                    throw new ScarabException(L10NKeySet.ExceptionIncompatibleIssueIds,
1245                            minId,
1246                            maxId);
1247                }
1248            }
1249            if (domain != null)
1250            {
1251                sb.append(AND).append(IssuePeer.ID_DOMAIN).append("='")
1252                    .append(domain).append('\'');
1253            }
1254            if (prefix != null)
1255            {
1256                sb.append(AND).append(IssuePeer.ID_PREFIX).append("='")
1257                    .append(prefix).append('\'');
1258            }
1259            where.append(AND).append(sb);
1260        }
1261    }
1262
1263
1264    /**
1265     * give reasonable defaults if module code was not specified
1266     */

1267    private void setDefaults(FederatedId minFid,
1268                             FederatedId maxFid)
1269        throws Exception JavaDoc
1270    {
1271        Module module = getModule();
1272        if (module != null)
1273        {
1274            if (minFid != null && minFid.getDomain() == null)
1275            {
1276                minFid.setDomain(module.getScarabInstanceId());
1277            }
1278            if (maxFid != null && maxFid.getDomain() == null)
1279            {
1280                maxFid.setDomain(module.getScarabInstanceId());
1281            }
1282            if (minFid != null && minFid.getPrefix() == null)
1283            {
1284                minFid.setPrefix(module.getCode());
1285            }
1286        }
1287        if (maxFid != null && maxFid.getPrefix() == null)
1288        {
1289            if (minFid == null)
1290            {
1291                maxFid.setPrefix(module.getCode());
1292            }
1293            else
1294            {
1295                maxFid.setPrefix(minFid.getPrefix());
1296            }
1297        }
1298    }
1299
1300    /**
1301     * Attempts to parse a atring as a date, first using the locale-sepcific
1302     * short date format, and then the ISO standard "yyyy-mm-dd". If it sees
1303     * a ':' character in the date string then the string will be interpreted
1304     * as a date <b>and</b> time. Throws a ParseException if the String does
1305     * not contain a suitable format.
1306     *
1307     * @param dateString a <code>String</code> value
1308     * @param locale the locale to use when determining the date patterns
1309     * to try.
1310     * @param addTwentyFourHours if no time is given in the date string and
1311     * this flag is true, then 24 hours - 1 msec will be added to the date.
1312     * @return a <code>Date</code> value
1313     */

1314    public Date JavaDoc parseDate(String JavaDoc dateString,
1315                          boolean addTwentyFourHours)
1316        throws ParseException JavaDoc
1317    {
1318        Date JavaDoc date = null;
1319        if (dateString != null)
1320        {
1321            if (dateString.indexOf(':') == -1)
1322            {
1323                //
1324
// First try to parse the date using the current
1325
// locale. If that doesn't work, then try the
1326
// ISO format.
1327
//
1328
String JavaDoc[] patterns = {
1329                    Localization.getString(this.locale, "ShortDatePattern"),
1330                    ScarabConstants.ISO_DATE_PATTERN };
1331                date = parseDate(dateString, patterns);
1332                
1333                // one last try with the default locale format
1334
if (date == null)
1335                {
1336                    //
1337
// If this fails, then we want the parse exception
1338
// to propogate. That's why we don't use
1339
// parseDateWithFormat() here.
1340
//
1341
date = DateFormat.getDateInstance().parse(dateString);
1342                }
1343
1344                // add 24 hours to max date so it is inclusive
1345
if (addTwentyFourHours)
1346                {
1347                    date.setTime(date.getTime() + 86399999);
1348                }
1349            }
1350            else
1351            {
1352                //
1353
// First try to parse the date using the current
1354
// locale. If that doesn't work, then try the
1355
// ISO format.
1356
//
1357
String JavaDoc[] patterns = {
1358                    Localization.getString(this.locale, "ShortDateTimePattern"),
1359                    ScarabConstants.ISO_DATETIME_PATTERN };
1360                date = parseDate(dateString, patterns);
1361        
1362                // one last try with the default locale format
1363
if (date == null)
1364                {
1365                    date = DateFormat.getDateTimeInstance().parse(dateString);
1366                }
1367            }
1368        }
1369        
1370        return date;
1371    }
1372    
1373    /**
1374     * Attempts to parse a String as a Date, trying each pattern in
1375     * turn until the string is successfully parsed or all patterns
1376     * have been tried.
1377     *
1378     * @param s a <code>String</code> value that should be converted
1379     * to a <code>Date</code>.
1380     * @param patterns if no time is given in the date string and
1381     * this flag is true, then 24 hours - 1 msec will be added to the date.
1382     * @return the equivalent <code>Date</code> if the string could
1383     * be parsed.
1384     * @throws ParseException if input String is null, or the string
1385     * could not be parsed.
1386     */

1387    private Date JavaDoc parseDate(String JavaDoc s, String JavaDoc[] patterns)
1388        throws ParseException JavaDoc
1389    {
1390        /* FIXME: the contract for this method is strange
1391           it is returning a null value when encountering a ParseException,
1392           and throwing a ParseException when having a wrong input*/

1393        if (s == null)
1394        {
1395            throw new ParseException JavaDoc("Input string was null", -1); //EXCEPTION
1396
}
1397
1398        if (formatter == null)
1399        {
1400            formatter = new SimpleDateFormat JavaDoc();
1401        }
1402        
1403        for (int i = 0; i < patterns.length; i++)
1404        {
1405            formatter.applyPattern(patterns[i]);
1406            Date JavaDoc date = parseDateWithFormat(s, formatter);
1407            
1408            if (date != null)
1409            {
1410                return date;
1411            }
1412        }
1413        
1414        throw new ParseException JavaDoc("Date could not be parsed with any"
1415                                 + " of the provided date patterns.", -1); //EXCEPTION
1416
}
1417    
1418    private Date JavaDoc parseDateWithFormat(String JavaDoc dateString, DateFormat JavaDoc format) {
1419        try
1420        {
1421            return format.parse(dateString);
1422        }
1423        catch (ParseException JavaDoc ex)
1424        {
1425            return null;
1426        }
1427    }
1428
1429
1430    private void addDateRange(String JavaDoc column, Date JavaDoc minUtilDate,
1431                              Date JavaDoc maxUtilDate, StringBuffer JavaDoc sb)
1432        throws Exception JavaDoc
1433    {
1434        // check limits to see which ones are present
1435
// if neither are present, do nothing
1436
if (minUtilDate != null || maxUtilDate != null)
1437        {
1438            DB adapter = Torque.getDB(Torque.getDefaultDB());
1439            if (minUtilDate == null)
1440            {
1441                sb.append(column).append('<')
1442                    .append(adapter.getDateString(maxUtilDate));
1443            }
1444            else if (maxUtilDate == null)
1445            {
1446                sb.append(column).append(">=")
1447                    .append(adapter.getDateString(minUtilDate));
1448            }
1449            else
1450            {
1451                // make sure min id is less than max id and that the character
1452
// parts are equal otherwise skip the query, there are no
1453
// matches
1454
if (minUtilDate.before(maxUtilDate))
1455                {
1456                    sb.append(column).append(">=")
1457                        .append(adapter.getDateString(minUtilDate));
1458                    sb.append(AND);
1459                    sb.append(column).append('<')
1460                        .append(adapter.getDateString(maxUtilDate));
1461                }
1462                else
1463                {
1464                    throw new ScarabException(L10NKeySet.ExceptionMaxdateBeforeMindate,
1465                            this.maxDate,
1466                            minUtilDate);
1467                }
1468            }
1469        }
1470    }
1471
1472
1473    /**
1474     * Returns a List of matching issues. if no OptionAttributes were
1475     * found in the input list, criteria is unaltered.
1476     *
1477     * @param attValues a <code>List</code> value
1478     */

1479    private void addSelectedAttributes(StringBuffer JavaDoc fromClause,
1480                                       List JavaDoc attValues, Set JavaDoc tableAliases)
1481        throws Exception JavaDoc
1482    {
1483        Map JavaDoc attrMap = new HashMap JavaDoc((int)(attValues.size()*1.25));
1484        for (int j=0; j<attValues.size(); j++)
1485        {
1486            AttributeValue multiAV = (AttributeValue)attValues.get(j);
1487            if (multiAV instanceof OptionAttribute)
1488            {
1489                Integer JavaDoc index = multiAV.getAttributeId();
1490                List JavaDoc options = (List JavaDoc)attrMap.get(index);
1491                if (options == null)
1492                {
1493                    options = new ArrayList JavaDoc();
1494                    attrMap.put(index, options);
1495                }
1496                
1497                //pull any chained values out to create a flat list
1498
List JavaDoc chainedValues = multiAV.getValueList();
1499                for (int i=0; i<chainedValues.size(); i++)
1500                {
1501                    AttributeValue aval = (AttributeValue)chainedValues.get(i);
1502                    buildOptionList(options, aval);
1503                }
1504            }
1505        }
1506
1507        for (Iterator JavaDoc i=attrMap.entrySet().iterator(); i.hasNext();)
1508        {
1509            Map.Entry JavaDoc entry = (Map.Entry JavaDoc)i.next();
1510            String JavaDoc alias = "av" + entry.getKey();
1511            List JavaDoc options = (List JavaDoc)entry.getValue();
1512            String JavaDoc c2 = null;
1513            if (options.size() == 1)
1514            {
1515                c2 = alias + '.' + AV_OPTION_ID + '='
1516                    + options.get(0);
1517            }
1518            else
1519            {
1520                c2 = alias + '.' + AV_OPTION_ID + " IN ("
1521                    + StringUtils.join(options.iterator(), ",") + ')';
1522            }
1523            joinCounter++;
1524            String JavaDoc joinClause = INNER_JOIN + AttributeValuePeer.TABLE_NAME
1525                + ' ' + alias + " ON (" +
1526                alias + '.' + AV_ISSUE_ID + '=' + IssuePeer.ISSUE_ID +
1527                AND + c2 + AND +
1528                alias + '.' + "DELETED=0" + ')';
1529            // might want to add redundant av2.ISSUE_ID=av5.ISSUE_ID. might
1530
// not be necessary with sql92 join format?
1531
fromClause.append(joinClause);
1532            tableAliases.add(alias);
1533        }
1534    }
1535
1536    /**
1537     * <p>This method builds a Criterion for a single attribute value.
1538     * It is used in the addOptionAttributes method.</p>
1539     * <p>The attribute value is basically the attribute name/id
1540     * + its value. Since some option (picklist) attributes are
1541     * hierachical, we need to add any child values of the given
1542     * attribute value. For example, assume we have an attribute named
1543     * "Operating System". This might have values in a hierarchy like
1544     * so:</p>
1545     * <pre>
1546     * All
1547     * Windows
1548     * NT
1549     * 2000
1550     * XP
1551     * Unix
1552     * Linux
1553     * Solaris
1554     * Tru64
1555     * </pre>
1556     * <p>If the user selects the "Windows" value in a query, we want
1557     * to include any issues that have "Windows" as this attribute's
1558     * value, and also "NT", "2000", and "XP".</p>
1559     * <p>All the appropriate attribute values are added to the 'options'
1560     * list as RModuleOption objects.</p>
1561     *
1562     * @param aval an <code>AttributeValue</code> value
1563     * @return a <code>Criteria.Criterion</code> value
1564     */

1565    private void buildOptionList(List JavaDoc options, AttributeValue aval)
1566        throws Exception JavaDoc
1567    {
1568        List JavaDoc descendants = null;
1569        // it would be a more correct query to separate the descendant
1570
// options by module and do something like
1571
// ... (module_id=1 and option_id in (1,2,3)) OR (module_id=5...
1572
// but we are not checking which options are active here so i
1573
// don't think the complexity of the query is needed. might want
1574
// to revisit, especially the part about ignoring active setting.
1575
if (isXMITSearch())
1576        {
1577            descendants =
1578                mitList.getDescendantsUnion(aval.getAttributeOption());
1579        }
1580        else
1581        {
1582            IssueType issueType = getIssueType();
1583            
1584            //
1585
// This call checks whether the attribute value is available
1586
// to the current module. If not, then no attribute options
1587
// are added to the list.
1588
//
1589
RModuleOption rmo = getModule()
1590                .getRModuleOption(aval.getAttributeOption(), issueType);
1591            if (rmo != null)
1592            {
1593                descendants = rmo.getDescendants(issueType);
1594            }
1595        }
1596        
1597        //
1598
// Include the selected attribute value as one of the options
1599
// to search for.
1600
//
1601
options.add(aval.getOptionId());
1602        
1603        if (descendants != null && !descendants.isEmpty())
1604        {
1605            //
1606
// Add all applicable child attribute options to the list as well.
1607
//
1608
for (Iterator JavaDoc i = descendants.iterator(); i.hasNext();)
1609            {
1610                options.add(((RModuleOption)i.next())
1611                    .getOptionId());
1612            }
1613        }
1614    }
1615
1616    private void addUserAndCreatedDateCriteria(StringBuffer JavaDoc from,
1617                                               StringBuffer JavaDoc where)
1618        throws Exception JavaDoc
1619    {
1620        String JavaDoc dateRangeSql = null;
1621        if (getMinDate() != null || getMaxDate() != null)
1622        {
1623            StringBuffer JavaDoc sbdate = new StringBuffer JavaDoc();
1624            Date JavaDoc minUtilDate = parseDate(getMinDate(), false);
1625            Date JavaDoc maxUtilDate = parseDate(getMaxDate(), true);
1626            addDateRange(ACTIVITYSETALIAS + '.' + CREATED_DATE,
1627                         minUtilDate, maxUtilDate, sbdate);
1628            dateRangeSql = sbdate.toString();
1629        }
1630        
1631        if (userIdList == null || userIdList.isEmpty())
1632        {
1633            if (dateRangeSql != null)
1634            {
1635                joinCounter++;
1636                // just dates
1637
from.append(INNER_JOIN).append(ActivitySetPeer.TABLE_NAME)
1638                    .append(' ').append(ACTIVITYSETALIAS).append(ON).append(
1639                    ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID)
1640                    .append(AND).append(dateRangeSql)
1641                    .append(')');
1642            }
1643        }
1644        else
1645        {
1646            List JavaDoc anyUsers = null;
1647            List JavaDoc creatorUsers = null;
1648            Map JavaDoc attrUsers = null;
1649
1650            int maxUsers = userIdList.size();
1651            // separate users by attribute, Created_by, and Any
1652
for (int i =0; i<maxUsers; i++)
1653            {
1654                String JavaDoc userId = (String JavaDoc)userIdList.get(i);
1655                String JavaDoc attrId = (String JavaDoc)userSearchCriteriaList.get(i);
1656                if (attrId == null || ANY_KEY.equals(attrId))
1657                {
1658                    if (anyUsers == null)
1659                    {
1660                        anyUsers = new ArrayList JavaDoc(maxUsers);
1661                    }
1662                    anyUsers.add(userId);
1663                }
1664                else if (CREATED_BY_KEY.equals(attrId))
1665                {
1666                    if (creatorUsers == null)
1667                    {
1668                        creatorUsers = new ArrayList JavaDoc(maxUsers);
1669                    }
1670                    creatorUsers.add(userId);
1671                }
1672                else
1673                {
1674                    // using a map here seems like overkill, but it
1675
// makes the logic easier
1676
if (attrUsers == null)
1677                    {
1678                        attrUsers = new HashMap JavaDoc(maxUsers);
1679                    }
1680                    List JavaDoc userIds = (List JavaDoc)attrUsers.get(attrId);
1681                    if (userIds == null)
1682                    {
1683                        userIds = new ArrayList JavaDoc(maxUsers);
1684                        attrUsers.put(attrId, userIds);
1685                    }
1686                    userIds.add(userId);
1687                }
1688            }
1689
1690            // All users are compared using OR, so use a single alias
1691
// for activities related to users.
1692
joinCounter++;
1693            StringBuffer JavaDoc fromClause = new StringBuffer JavaDoc(100);
1694            fromClause.append(INNER_JOIN).append(ActivityPeer.TABLE_NAME)
1695                .append(' ').append(ACTIVITYALIAS).append(ON)
1696                .append(ACTIVITYALIAS_ISSUE_ID__EQUALS__ISSUEPEER_ISSUE_ID);
1697
1698            StringBuffer JavaDoc attrCrit = null;
1699            if (anyUsers != null)
1700            {
1701                attrCrit = new StringBuffer JavaDoc(50);
1702                attrCrit.append('(');
1703                addUserActivityFragment(attrCrit, anyUsers);
1704                attrCrit.append(')');
1705            }
1706            
1707            // Add sql fragment for each attribute. The sql is similar
1708
// to the one used for Any users with the addition of attribute
1709
// criteria
1710
if (attrUsers != null)
1711            {
1712                for (Iterator JavaDoc i = attrUsers.entrySet().iterator(); i.hasNext();)
1713                {
1714                    if (attrCrit == null)
1715                    {
1716                        attrCrit = new StringBuffer JavaDoc();
1717                    }
1718                    else
1719                    {
1720                        attrCrit.append(OR);
1721                    }
1722                
1723                    Map.Entry JavaDoc entry = (Map.Entry JavaDoc)i.next();
1724                    String JavaDoc attrId = (String JavaDoc)entry.getKey();
1725                    List JavaDoc userIds = (List JavaDoc)entry.getValue();
1726                    attrCrit.append('(');
1727                    addUserActivityFragment(attrCrit, userIds);
1728                    attrCrit.append(AND +
1729                        ACTIVITYALIAS + '.' + ATTRIBUTE_ID + '=' + attrId);
1730                    attrCrit.append(')');
1731                }
1732            }
1733
1734            boolean isAddActivitySet = anyUsers != null || creatorUsers != null
1735                || dateRangeSql != null;
1736            String JavaDoc whereClause = null;
1737            if (isAddActivitySet)
1738            {
1739                if (attrCrit != null)
1740                {
1741                    whereClause = '(' + attrCrit.toString() + ')';
1742                }
1743
1744                joinCounter++;
1745                fromClause.append(')').append(INNER_JOIN)
1746                    .append(ActivitySetPeer.TABLE_NAME)
1747                    .append(' ').append(ACTIVITYSETALIAS).append(ON).append(
1748                    ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID);
1749
1750                if (anyUsers != null || creatorUsers != null)
1751                {
1752                    List JavaDoc anyAndCreators = new ArrayList JavaDoc(maxUsers);
1753                    if (anyUsers != null)
1754                    {
1755                        anyAndCreators.addAll(anyUsers);
1756                    }
1757                    if (creatorUsers != null)
1758                    {
1759                        anyAndCreators.addAll(creatorUsers);
1760                    }
1761
1762                    // we can add this to the join condition, if created-only
1763
// query otherwise it needs to go in the where clause
1764
String JavaDoc createdBySqlFragment =
1765                        ACTIVITYSETALIAS + '.' + CREATED_BY;
1766                    if (anyAndCreators.size() == 1)
1767                    {
1768                        createdBySqlFragment +=
1769                            '=' + anyAndCreators.get(0).toString();
1770                    }
1771                    else
1772                    {
1773                        createdBySqlFragment += IN +
1774                            StringUtils.join(anyAndCreators.iterator(), ",")
1775                            + ')';
1776                    }
1777                
1778                    if (anyUsers != null || attrUsers != null)
1779                    {
1780                        fromClause.append(')');
1781                        whereClause = '(' + whereClause + OR +
1782                            createdBySqlFragment + ')';
1783                        if (dateRangeSql != null)
1784                        {
1785                            whereClause += AND + dateRangeSql;
1786                        }
1787                    }
1788                    else
1789                    {
1790                        fromClause.append(AND).append(createdBySqlFragment);
1791                        if (dateRangeSql != null)
1792                        {
1793                            fromClause.append(AND).append(dateRangeSql);
1794                        }
1795                        fromClause.append(')');
1796                    }
1797                }
1798                else // dateRangeSql will not be null
1799
{
1800                    fromClause.append(AND).append(dateRangeSql).append(')');
1801                }
1802            }
1803            else
1804            {
1805                // we only had single-attribute users and no date criteria.
1806
// attrCrit will not be null, because we had to have at
1807
// least one user or we'd not be here
1808
fromClause.append(AND).append('(').append(attrCrit)
1809                    .append("))");
1810            }
1811
1812            from.append(fromClause.toString());
1813            if (whereClause != null)
1814            {
1815                where.append(AND).append(whereClause);
1816            }
1817        }
1818    }
1819
1820    private void addUserActivityFragment(StringBuffer JavaDoc sb, List JavaDoc userIds)
1821    {
1822        sb.append(ACTIVITYALIAS + '.' + END_DATE +
1823                  IS_NULL + AND + ACTIVITYALIAS_NEW_USER_ID);
1824        if (userIds.size() == 1)
1825        {
1826            sb.append('=').append(userIds.get(0));
1827        }
1828        else
1829        {
1830            sb.append(IN +
1831                       StringUtils.join(userIds.iterator(), ",") + ')');
1832        }
1833    }
1834
1835
1836    private Long JavaDoc[] getTextMatches(List JavaDoc attValues, boolean mergeTextResults)
1837        throws Exception JavaDoc
1838    {
1839        boolean searchCriteriaExists = false;
1840        Long JavaDoc[] matchingIssueIds = null;
1841        SearchIndex searchIndex = SearchFactory.getInstance();
1842        if (searchIndex == null)
1843        {
1844            // Check your configuration.
1845
throw new Exception JavaDoc("No index available to search"); //EXCEPTION
1846
}
1847        if (getSearchWords() != null && getSearchWords().length() != 0)
1848        {
1849            searchIndex.addQuery(getTextScope(), getSearchWords());
1850            searchCriteriaExists = true;
1851        }
1852        else
1853        {
1854            for (int i=0; i<attValues.size(); i++)
1855            {
1856                AttributeValue aval = (AttributeValue)attValues.get(i);
1857                if (aval.getValue() != null
1858                        && aval.getValue().length() != 0)
1859                {
1860                    /** Parser will only be != null if we are "searching" issues **/
1861                    if (parser != null && aval instanceof DateAttribute)
1862                    {
1863                        String JavaDoc auxDate = parser.getString("attv__" + aval.getAttributeId().intValue() + "val_aux");
1864                        if (auxDate == null)
1865                            auxDate = "";
1866                        else
1867                            auxDate = DateAttribute.internalDateFormat(auxDate.trim(), L10N.get(L10NKeySet.ShortDatePattern));
1868                        aval.setValue(DateAttribute.internalDateFormat(aval.getValue(), L10N.get(L10NKeySet.ShortDatePattern)));
1869                        searchCriteriaExists = true;
1870                        Integer JavaDoc[] id = {aval.getAttributeId()};
1871                        if (auxDate.equals(aval.getValue()))
1872                            searchIndex.addQuery(id, aval.getValue());
1873                        else
1874                            searchIndex.addQuery(id, SearchIndex.TEXT + ":["+aval.getValue() + " TO " + auxDate + "]");
1875                    }
1876                    else if (aval instanceof StringAttribute)
1877                    {
1878                        searchCriteriaExists = true;
1879                        Integer JavaDoc[] id = {aval.getAttributeId()};
1880                        searchIndex
1881                            .addQuery(id, aval.getValue());
1882                    }
1883                }
1884                
1885            }
1886        }
1887
1888        // add comment attachments
1889
String JavaDoc commentQuery = getCommentQuery();
1890        if (commentQuery != null && commentQuery.trim().length() > 0)
1891        {
1892            Integer JavaDoc[] id = {AttachmentTypePeer.COMMENT_PK};
1893            searchIndex.addAttachmentQuery(id, commentQuery);
1894            searchCriteriaExists = true;
1895        }
1896
1897        if (searchCriteriaExists)
1898        {
1899            try
1900            {
1901                matchingIssueIds = searchIndex.getRelatedIssues(mergeTextResults);
1902            }
1903            catch (Exception JavaDoc e)
1904            {
1905                SearchFactory.releaseInstance(searchIndex);
1906                throw e;
1907            }
1908        }
1909
1910        SearchFactory.releaseInstance(searchIndex);
1911        return matchingIssueIds;
1912
1913    }
1914
1915    private void addStateChangeQuery(StringBuffer JavaDoc from)
1916        throws Exception JavaDoc
1917    {
1918        Integer JavaDoc oldOptionId = getStateChangeFromOptionId();
1919        Integer JavaDoc newOptionId = getStateChangeToOptionId();
1920        Date JavaDoc minUtilDate = parseDate(getStateChangeFromDate(), false);
1921        Date JavaDoc maxUtilDate = parseDate(getStateChangeToDate(), true);
1922        if ((oldOptionId != null && !oldOptionId.equals(NUMBERKEY_0))
1923            || (newOptionId != null && !newOptionId.equals(NUMBERKEY_0))
1924            || minUtilDate != null || maxUtilDate != null)
1925        {
1926            joinCounter++;
1927            from.append(INNER_JOIN + ActivityPeer.TABLE_NAME + ON +
1928                        ActivityPeer.ISSUE_ID + '=' + IssuePeer.ISSUE_ID);
1929
1930            if (oldOptionId == null && newOptionId == null)
1931            {
1932                from.append(AND).append(ActivityPeer.ATTRIBUTE_ID)
1933                    .append('=').append(getStateChangeAttributeId());
1934            }
1935            else
1936            {
1937                if (newOptionId != null && !newOptionId.equals(NUMBERKEY_0))
1938                {
1939                    from.append(AND).append(ActivityPeer.NEW_OPTION_ID)
1940                        .append('=').append(newOptionId);
1941                }
1942                if (oldOptionId != null && !oldOptionId.equals(NUMBERKEY_0))
1943                {
1944                    from.append(AND).append(ActivityPeer.OLD_OPTION_ID)
1945                        .append('=').append(oldOptionId);
1946                }
1947            }
1948            from.append(')');
1949
1950            // add dates, if given
1951
if (minUtilDate != null || maxUtilDate != null)
1952            {
1953                joinCounter++;
1954                from.append(INNER_JOIN + ActivitySetPeer.TABLE_NAME + ON +
1955                             ActivitySetPeer.TRANSACTION_ID + '=' +
1956                             ActivityPeer.TRANSACTION_ID);
1957                from.append(AND);
1958
1959                addDateRange(ActivitySetPeer.CREATED_DATE,
1960                             minUtilDate, maxUtilDate, from);
1961                
1962                from.append(')');
1963            }
1964        }
1965    }
1966
1967    private Long JavaDoc[] addCoreSearchCriteria(StringBuffer JavaDoc fromClause,
1968                                              StringBuffer JavaDoc whereClause,
1969                                              Set JavaDoc tableAliases,
1970                                              boolean mergePartialQueryResults)
1971        throws Exception JavaDoc
1972    {
1973        if (isXMITSearch())
1974        {
1975            Criteria crit = new Criteria();
1976            mitList.addToCriteria(crit);
1977            String JavaDoc sql = crit.toString();
1978            int wherePos = sql.indexOf(" WHERE ");
1979            whereClause.append(sql.substring(wherePos + 7));
1980        }
1981        else
1982        {
1983            whereClause.append(IssuePeer.MODULE_ID).append('=')
1984                .append(getModule().getModuleId());
1985            whereClause.append(AND).append(IssuePeer.TYPE_ID).append('=')
1986                .append(getIssueType().getIssueTypeId());
1987        }
1988        whereClause.append(AND).append(IssuePeer.DELETED).append("=0");
1989
1990        // add option values
1991
lastUsedAVList = new ArrayList JavaDoc(getAttributeValues());
1992
1993        // remove unset AttributeValues before searching
1994
List JavaDoc setAttValues = removeUnsetValues(lastUsedAVList);
1995        addSelectedAttributes(fromClause, setAttValues, tableAliases);
1996
1997        // search for issues based on text
1998
Long JavaDoc[] matchingIssueIds = getTextMatches(setAttValues, mergePartialQueryResults);
1999
2000        if (matchingIssueIds == null || matchingIssueIds.length > 0)
2001        {
2002            addIssueIdRange(whereClause);
2003            //addMinimumVotes(whereClause);
2004

2005            // add user values
2006
addUserAndCreatedDateCriteria(fromClause, whereClause);
2007
2008            // add text search matches
2009
addIssuePKsCriteria(whereClause, matchingIssueIds);
2010
2011            // state change query
2012
addStateChangeQuery(fromClause);
2013        }
2014        return matchingIssueIds;
2015    }
2016
2017    private void addIssuePKsCriteria(StringBuffer JavaDoc sb, Long JavaDoc[] ids)
2018    {
2019       if (ids != null && ids.length > 0)
2020       {
2021           sb.append(AND).append(IssuePeer.ISSUE_ID).append(IN)
2022               .append(StringUtils.join(ids, ",")).append(')');
2023       }
2024    }
2025
2026    /**
2027     * Get a List of Issues that match the criteria given by this
2028     * SearchIssue's searchWords and the quick search attribute values.
2029     * Perform a logical AND on partial queries (in text searches)
2030     * @return a <code>List</code> value
2031     * @exception Exception if an error occurs
2032     */

2033    public IteratorWithSize getQueryResults()
2034        throws ComplexQueryException, Exception JavaDoc
2035    {
2036        return getQueryResults(false);
2037    }
2038
2039    /**
2040     * Get a List of Issues that match the criteria given by this
2041     * SearchIssue's searchWords and the quick search attribute values.
2042     * if (mergePartialQueries==true) perform a logical OR on partial queries,
2043     * otherwise perform a logical AND on partial queries.
2044     * @return a <code>List</code> value
2045     * @exception Exception if an error occurs
2046     */

2047    public IteratorWithSize getQueryResults(boolean mergePartialQueries)
2048        throws ComplexQueryException, Exception JavaDoc
2049    {
2050        checkModified();
2051        if (!isSearchAllowed)
2052        {
2053            lastQueryResults = IteratorWithSize.EMPTY;
2054        }
2055        else if (lastQueryResults == null)
2056        {
2057            Set JavaDoc tableAliases = new HashSet JavaDoc();
2058            StringBuffer JavaDoc from = new StringBuffer JavaDoc();
2059            StringBuffer JavaDoc where = new StringBuffer JavaDoc();
2060            joinCounter = 0;
2061            Long JavaDoc[] matchingIssueIds = addCoreSearchCriteria(from, where,
2062                                                            tableAliases,
2063                                                            mergePartialQueries);
2064            if (joinCounter > MAX_INNER_JOIN)
2065            {
2066                //WORK [HD} Need refactoring here. How can a user
2067
// create too complex queries ?
2068
throw new ComplexQueryException(L10NKeySet.ExceptionQueryTooComplex);
2069            }
2070            // the matchingIssueIds are text search matches. if length == 0,
2071
// then no need to search further. if null then there was no
2072
// text to search, so continue the search process.
2073
if (matchingIssueIds == null || matchingIssueIds.length > 0)
2074            {
2075                lastQueryResults = getQueryResults(from, where, tableAliases, mergePartialQueries);
2076            }
2077            else
2078            {
2079                lastQueryResults = IteratorWithSize.EMPTY;
2080            }
2081        }
2082        
2083        return lastQueryResults;
2084    }
2085
2086    public int getIssueCount()
2087    throws ComplexQueryException, Exception JavaDoc
2088    {
2089        
2090        return getIssueCount(false);
2091    }
2092
2093    public int getIssueCount(boolean mergePartialQueries)
2094        throws ComplexQueryException, Exception JavaDoc
2095    {
2096        checkModified();
2097        int count = 0;
2098        if (isSearchAllowed)
2099        {
2100            if (lastTotalIssueCount >= 0)
2101            {
2102                count = lastTotalIssueCount;
2103            }
2104            else
2105            {
2106                count = countFromDB(mergePartialQueries);
2107            }
2108            lastTotalIssueCount = count;
2109        }
2110
2111        return count;
2112    }
2113
2114    private int countFromDB(boolean mergePartialQueries)
2115        throws ComplexQueryException, Exception JavaDoc
2116    {
2117        int count = 0;
2118        StringBuffer JavaDoc from = new StringBuffer JavaDoc();
2119        StringBuffer JavaDoc where = new StringBuffer JavaDoc();
2120        joinCounter = 0;
2121        Long JavaDoc[] matchingIssueIds = addCoreSearchCriteria(from, where,
2122                                                        new HashSet JavaDoc(),
2123                                                        mergePartialQueries);
2124        if (joinCounter > MAX_INNER_JOIN)
2125        {
2126            //WORK [HD} Need refactoring here. How can a user
2127
// create too complex queries ?
2128
throw new ComplexQueryException(L10NKeySet.ExceptionQueryTooComplex);
2129        }
2130        
2131        if (matchingIssueIds == null || matchingIssueIds.length > 0)
2132        {
2133            StringBuffer JavaDoc sql = new StringBuffer JavaDoc("SELECT count(DISTINCT ");
2134            sql.append(IssuePeer.ISSUE_ID).append(')').append(" FROM ")
2135                .append(IssuePeer.TABLE_NAME);
2136            if (from.length() > 0)
2137            {
2138                sql.append(' ').append(from.toString());
2139            }
2140            if (where.length() > 0)
2141            {
2142                sql.append(WHERE).append(where.toString());
2143            }
2144            String JavaDoc countSql = sql.toString();
2145            
2146            Connection JavaDoc localCon = conn;
2147            Statement JavaDoc stmt = null;
2148            try
2149            {
2150                if (localCon == null)
2151                {
2152                    localCon = Torque.getConnection();
2153                }
2154                long startTime = System.currentTimeMillis();
2155                stmt = localCon.createStatement();
2156                ResultSet JavaDoc resultSet = stmt.executeQuery(countSql);
2157                if (resultSet.next())
2158                {
2159                    count = resultSet.getInt(1);
2160                }
2161                logTime(countSql, System.currentTimeMillis() - startTime,
2162                        50L, 500L);
2163            }
2164            finally
2165            {
2166                if (stmt != null)
2167                {
2168                    stmt.close();
2169                }
2170                if (conn == null && localCon != null)
2171                {
2172                    localCon.close();
2173                }
2174            }
2175        }
2176        return count;
2177    }
2178
2179    private static final String JavaDoc LOGGER = "org.apache.torque";
2180    private void logTime(String JavaDoc message, long time,
2181                         long infoLimit, long warnLimit)
2182    {
2183        if (time > warnLimit)
2184        {
2185            Log.get(LOGGER).warn(message + "\nTime = " + time + " ms");
2186        }
2187        else if (time > infoLimit)
2188        {
2189            Logger log = Log.get(LOGGER);
2190            if (log.isInfoEnabled())
2191            {
2192                log.info(message + "\nTime = " + time + " ms");
2193            }
2194        }
2195        else
2196        {
2197            Logger log = Log.get(LOGGER);
2198            if (log.isDebugEnabled())
2199            {
2200                log.info(message + "\nTime = " + time + " ms");
2201            }
2202        }
2203    }
2204
2205    private String JavaDoc setupSortColumn(Integer JavaDoc sortAttrId,
2206                                   StringBuffer JavaDoc sortOuterJoin,
2207                                   Set JavaDoc tableAliases)
2208        throws TorqueException
2209    {
2210        String JavaDoc alias = AV + sortAttrId;
2211        if (!tableAliases.contains(alias))
2212        {
2213            sortOuterJoin.append(LEFT_OUTER_JOIN)
2214                .append(AttributeValuePeer.TABLE_NAME).append(' ')
2215                .append(alias).append(ON)
2216                .append(IssuePeer.ISSUE_ID).append('=')
2217                .append(alias).append(".ISSUE_ID AND ").append(alias)
2218                .append(".DELETED=0 AND ").append(alias)
2219                .append(".ATTRIBUTE_ID=").append(sortAttrId).append(')');
2220        }
2221            String JavaDoc sortColumn;
2222            Attribute att = AttributeManager.getInstance(sortAttrId);
2223            if (att.isOptionAttribute())
2224            {
2225                // add the sort column
2226
sortColumn = SORTRMO_PREFERRED_ORDER;
2227                // join the RMO table to the alias we are sorting
2228
sortOuterJoin.append(BASE_OPTION_SORT_LEFT_JOIN).append(alias)
2229                    .append(DOT_OPTION_ID_PAREN);
2230            }
2231            else
2232            {
2233                sortColumn = alias + DOT_VALUE;
2234            }
2235            return sortColumn;
2236    }
2237
2238    private List JavaDoc getSearchSqlPieces(StringBuffer JavaDoc from, StringBuffer JavaDoc where,
2239                                    Set JavaDoc tableAliases)
2240        throws TorqueException
2241    {
2242        List JavaDoc searchStuff = new ArrayList JavaDoc(3);
2243        Integer JavaDoc sortAttrId = getSortAttributeId();
2244
2245        // Get matching issues, with sort criteria
2246
StringBuffer JavaDoc sql = getSelectStart();
2247        sql.append(',').append(IssuePeer.MODULE_ID)
2248            .append(',').append(IssuePeer.TYPE_ID);
2249        String JavaDoc sortColumn = null;
2250        StringBuffer JavaDoc sortOuterJoin = null;
2251        if (sortAttrId != null)
2252        {
2253            sortOuterJoin = new StringBuffer JavaDoc(128);
2254            sortColumn = setupSortColumn(sortAttrId, sortOuterJoin,
2255                                         tableAliases);
2256            sql.append(',').append(sortColumn);
2257        }
2258
2259        sql.append(FROM).append(IssuePeer.TABLE_NAME);
2260        if (from.length() > 0)
2261        {
2262            sql.append(' ').append(from.toString());
2263        }
2264        if (sortOuterJoin != null)
2265        {
2266            sql.append(sortOuterJoin);
2267        }
2268        if (where.length() > 0)
2269        {
2270            sql.append(WHERE).append(where.toString());
2271        }
2272        addOrderByClause(sql, sortColumn);
2273        searchStuff.add(sql.toString());
2274
2275        // add the attribute value columns that will be shown in the list.
2276
// these are joined using a left outer join, so the additional
2277
// columns do not affect the results of the search (no additional
2278
// criteria are added to the where clause.)
2279
List JavaDoc rmuas = getIssueListAttributeColumns();
2280        if (rmuas != null)
2281        {
2282            int valueListSize = rmuas.size();
2283            StringBuffer JavaDoc outerJoin = new StringBuffer JavaDoc(10 * valueListSize + 20);
2284
2285            int count = 0;
2286            int maxJoin = MAX_JOIN - 2;
2287            //List columnSqlList = new ArrayList(valueListSize/maxJoin + 1);
2288
StringBuffer JavaDoc partialSql = getSelectStart();
2289            tableAliases = new HashSet JavaDoc(MAX_JOIN);
2290            for (Iterator JavaDoc i = rmuas.iterator(); i.hasNext();)
2291            {
2292                RModuleUserAttribute rmua = (RModuleUserAttribute)i.next();
2293                Integer JavaDoc attrPK = rmua.getAttributeId();
2294          
2295                
2296                String JavaDoc id = attrPK.toString();
2297                String JavaDoc alias = AV + id;
2298                // add column to SELECT column clause
2299
partialSql.append(',').append(alias).append(DOT_VALUE);
2300                // if no criteria was specified for a displayed attribute
2301
// add it as an outer join
2302
if (!tableAliases.contains(alias)
2303                    && !attrPK.equals(sortAttrId))
2304                {
2305                    outerJoin.append(LEFT_OUTER_JOIN)
2306                        .append(AttributeValuePeer.TABLE_NAME).append(' ')
2307                        .append(alias).append(ON)
2308                        .append(IssuePeer.ISSUE_ID).append('=')
2309                        .append(alias).append(".ISSUE_ID AND ").append(alias)
2310                        .append(".DELETED=0 AND ").append(alias)
2311                        .append(".ATTRIBUTE_ID=").append(id).append(')');
2312                    tableAliases.add(alias);
2313                }
2314
2315                count++;
2316                if (count == maxJoin || !i.hasNext())
2317                {
2318                    ColumnBundle cb = new ColumnBundle();
2319                    cb.size = count;
2320                    if (sortAttrId != null)
2321                    {
2322                        cb.sortColumn = setupSortColumn(sortAttrId, outerJoin,
2323                                                        tableAliases);
2324                        partialSql.append(',').append(
2325                            cb.sortColumn);
2326                    }
2327                    cb.select = partialSql;
2328                    cb.outerJoins = outerJoin;
2329                    searchStuff.add(cb);
2330
2331                    partialSql = getSelectStart();
2332                    outerJoin = new StringBuffer JavaDoc(512);
2333                    tableAliases.clear();
2334                    count = 0;
2335                }
2336            }
2337        }
2338        return searchStuff;
2339    }
2340
2341    private IteratorWithSize getQueryResults(StringBuffer JavaDoc from,
2342                                             StringBuffer JavaDoc where,
2343                                             Set JavaDoc tableAliases,
2344                                             boolean mergePartialQueries)
2345        throws TorqueException, ComplexQueryException, Exception JavaDoc
2346    {
2347        // return a List of QueryResult objects
2348
IteratorWithSize result = null;
2349        try
2350        {
2351            if (conn == null)
2352            {
2353                conn = Torque.getConnection();
2354                connectionStartTime = System.currentTimeMillis();
2355            }
2356
2357            // The code currently always calls getIssueCount() after we run this
2358
// query. We can avoid having to use 2 connections by calling it
2359
// now using 'conn'. It leaves the possibility of running the two
2360
// queries within a transaction as well. We also use the result
2361
// here to avoid the more complex query if there are no results.
2362
int count = getIssueCount(mergePartialQueries);
2363            if (count > 0)
2364            {
2365                result = new QueryResultIterator(this, count, from, where,
2366                                                 tableAliases);
2367            }
2368            else
2369            {
2370                result = IteratorWithSize.EMPTY;
2371            }
2372        }
2373        catch (SQLException JavaDoc e)
2374        {
2375            close();
2376            throw e; //EXCEPTION
2377
}
2378        /*
2379        catch (TorqueException e)
2380        {
2381            close();
2382            throw e;
2383        }
2384        */

2385        return result;
2386    }
2387
2388    private void addOrderByClause(StringBuffer JavaDoc sql, String JavaDoc sortColumn)
2389    {
2390        if (sortColumn == null)
2391        {
2392            sql.append(ORDER_BY).append(IssuePeer.ID_PREFIX);
2393            sql.append(' ').append(getSortPolarity());
2394            sql.append(',').append(IssuePeer.ID_COUNT);
2395            sql.append(' ').append(getSortPolarity());
2396        }
2397        else
2398        {
2399            sql.append(ORDER_BY).append(sortColumn);
2400            sql.append(' ').append(getSortPolarity());
2401            // add pk sort so that rows can be combined easily
2402
sql.append(',').append(IssuePeer.ISSUE_ID).append(" ASC");
2403        }
2404    }
2405
2406    private StringBuffer JavaDoc getSelectStart()
2407    {
2408        StringBuffer JavaDoc sql = new StringBuffer JavaDoc(512);
2409        sql.append(SELECT_DISTINCT)
2410            .append(IssuePeer.ISSUE_ID).append(',')
2411            .append(IssuePeer.ID_PREFIX).append(',')
2412            .append(IssuePeer.ID_COUNT);
2413        return sql;
2414    }
2415
2416
2417    /**
2418     * Used by QueryResult to avoid multiple db hits in the event caching
2419     * is not being used application-wide. It is used if the IssueList.vm
2420     * template is printing the module names next to each issue id.
2421     * As this IssueSearch object is short-lived, use of a simple Map based
2422     * cache is ok, need to re-examine if the lifespan is increased.
2423     *
2424     * @param id an <code>Integer</code> value
2425     * @return a <code>Module</code> value
2426     * @exception TorqueException if an error occurs
2427     */

2428    Module getModule(Integer JavaDoc id)
2429        throws TorqueException
2430    {
2431        Module module = (Module)moduleMap.get(id);
2432        if (module == null)
2433        {
2434            module = ModuleManager.getInstance(id);
2435            moduleMap.put(id, module);
2436        }
2437        return module;
2438    }
2439    
2440    /**
2441     * Used by QueryResult to avoid multiple db hits in the event caching
2442     * is not being used application-wide. It is used if the IssueList.vm
2443     * template is printing the issue type names next to each issue id.
2444     * As this IssueSearch object is short-lived, use of a simple Map based
2445     * cache is ok, need to re-examine if the lifespan is increased.
2446     *
2447     * @param moduleId an <code>Integer</code> value
2448     * @param issueTypeId an <code>Integer</code> value
2449     * @return a <code>RModuleIssueType</code> value
2450     * @exception TorqueException if an error occurs
2451     */

2452    RModuleIssueType getRModuleIssueType(Integer JavaDoc moduleId, Integer JavaDoc issueTypeId)
2453        throws TorqueException
2454    {
2455        SimpleKey[] nks = {SimpleKey.keyFor(moduleId.intValue()),
2456                           SimpleKey.keyFor(issueTypeId.intValue())};
2457        ObjectKey key = new ComboKey(nks);
2458        RModuleIssueType rmit = (RModuleIssueType)rmitMap.get(key);
2459        if (rmit == null)
2460        {
2461            rmit = RModuleIssueTypeManager.getInstance(key);
2462            rmitMap.put(key, rmit);
2463        }
2464        return rmit;
2465    }
2466
2467    /**
2468     * Called by the garbage collector to release any database
2469     * resources associated with this query.
2470     *
2471     * @see #close()
2472     */

2473    protected void finalize()
2474        throws Throwable JavaDoc
2475    {
2476        try
2477        {
2478            if (conn != null)
2479            {
2480                Log.get(LOGGER)
2481                    .warn("Closing connection in " + this + " finalizer");
2482                // if this object was left this state it is very likely that
2483
// the IssueSearchFactory was not notified either.
2484
// We error on the side of possibly increasing the available
2485
// IssueSearch objects, over potentially freezing users out
2486
IssueSearchFactory.INSTANCE.notifyDone();
2487            }
2488        }
2489        finally
2490        {
2491            close();
2492            super.finalize();
2493        }
2494    }
2495
2496    /**
2497     * Releases any managed resources associated with this search
2498     * (e.g. database connections, etc.).
2499     */

2500    public void close()
2501    {
2502        if (conn != null)
2503        {
2504            // Be extremely paranoid about assuring that the database
2505
// connection is released to avoid leaks.
2506
try
2507            {
2508                Logger log = Log.get(LOGGER);
2509                if (log.isDebugEnabled())
2510                {
2511                    log.debug("Releasing issue search database connection");
2512                }
2513            }
2514            finally
2515            {
2516                try
2517                {
2518                    if (searchRS != null)
2519                    {
2520                        searchRS.close();
2521                        searchRS = null;
2522                    }
2523                }
2524                catch (Exception JavaDoc e)
2525                {
2526                    try
2527                    {
2528                        Log.get(LOGGER).warn(
2529                            "Unable to close jdbc Statement", e);
2530                    }
2531                    catch (Exception JavaDoc ignore)
2532                    {
2533                    }
2534                }
2535                try
2536                {
2537                    if (searchStmt != null)
2538                    {
2539                        searchStmt.close();
2540                        searchStmt = null;
2541                    }
2542                }
2543                catch (Exception JavaDoc e)
2544                {
2545                    try
2546                    {
2547                        Log.get(LOGGER).warn(
2548                            "Unable to close jdbc Statement", e);
2549                    }
2550                    catch (Exception JavaDoc ignore)
2551                    {
2552                    }
2553                }
2554
2555                try
2556                {
2557                    closeStatementsAndResultSets();
2558                    stmtList = null;
2559                    rsList = null;
2560                }
2561                catch (Exception JavaDoc e)
2562                {
2563                    try
2564                    {
2565                        Log.get(LOGGER).warn(
2566                            "Unable to close jdbc Statement", e);
2567                    }
2568                    catch (Exception JavaDoc ignore)
2569                    {
2570                    }
2571                }
2572                
2573                Torque.closeConnection(this.conn);
2574                this.conn = null;
2575                logTime(this +
2576                        " released database connection which was held for:",
2577                        System.currentTimeMillis() - connectionStartTime,
2578                        5000L, 30000L);
2579            }
2580        }
2581    }
2582
2583    
2584    private void closeStatementsAndResultSets()
2585        throws SQLException JavaDoc
2586    {
2587        if (rsList != null)
2588        {
2589            for (Iterator JavaDoc iter = rsList.iterator(); iter.hasNext();)
2590            {
2591                ((ResultSetAndSize)iter.next()).resultSet.close();
2592            }
2593            rsList.clear();
2594        }
2595        
2596        if (stmtList != null)
2597        {
2598            for (Iterator JavaDoc iter = stmtList.iterator(); iter.hasNext();)
2599            {
2600                ((Statement JavaDoc)iter.next()).close();
2601            }
2602            stmtList.clear();
2603        }
2604    }
2605    
2606    /**
2607     * The query currently being launched, so we can access any value from it.
2608     * @param query
2609     */

2610    public void setQuery(String JavaDoc query) throws Exception JavaDoc
2611    {
2612        this.parser = new StringValueParser();
2613        parser.parse(query, '&', '=', true);
2614    }
2615    
2616    /**
2617     * Allows setting the L10N tool for using when is needed to know
2618     * the user's locale (example, when parsing date parameters)
2619     * @param l10nTool
2620     */

2621    public void setLocalizationTool(ScarabLocalizationTool l10nTool)
2622    {
2623        this.L10N = l10nTool;
2624    }
2625
2626    private class QueryResultIterator implements IteratorWithSize
2627    {
2628        final IssueSearch search;
2629        final List JavaDoc searchStuff;
2630        final int size;
2631
2632        QueryResult[] cachedQRs;
2633
2634        /**
2635         * @param issues The issue query results.
2636         */

2637        private QueryResultIterator(IssueSearch search, int size,
2638                                    StringBuffer JavaDoc from, StringBuffer JavaDoc where,
2639                                    Set JavaDoc tableAliases)
2640            throws SQLException JavaDoc, TorqueException
2641        {
2642            this.search = search;
2643            this.size = size;
2644            searchStuff = getSearchSqlPieces(from, where, tableAliases);
2645
2646            int numQueries = searchStuff.size();
2647            stmtList = new ArrayList JavaDoc(numQueries);
2648            rsList = new ArrayList JavaDoc(numQueries);
2649            
2650            long queryStartTime = System.currentTimeMillis();
2651            searchStmt = conn.createStatement();
2652            String JavaDoc searchSql = (String JavaDoc)searchStuff.get(0);
2653            try
2654            {
2655                searchRS = searchStmt.executeQuery(searchSql);
2656                logTime(searchSql +
2657                    "\nTime to only execute the query, not return results.",
2658                    System.currentTimeMillis() - queryStartTime,
2659                    50L, 500L);
2660            }
2661            catch (SQLException JavaDoc e)
2662            {
2663                Log.get(LOGGER).warn("Search sql:\n" + searchSql +
2664                    "\nresulted in an exception: " + e.getMessage());
2665                throw e; //EXCEPTION
2666

2667            }
2668        }
2669
2670        public int size()
2671        {
2672            return size;
2673        }
2674
2675        // ----------------------------------------------------------------
2676
// Iterator implementation
2677

2678        private QueryResult nextQueryResult;
2679        public Object JavaDoc next()
2680        {
2681            if (hasNext())
2682            {
2683                hasNext = null;
2684                return nextQueryResult;
2685            }
2686            else
2687            {
2688                throw new NoSuchElementException JavaDoc("Iterator is exhausted"); //EXCEPTION
2689
}
2690        }
2691
2692
2693        private Boolean JavaDoc hasNext;
2694        public boolean hasNext()
2695        {
2696            if (hasNext == null)
2697            {
2698                hasNext = (prepareNextQueryResult())
2699                    ? Boolean.TRUE : Boolean.FALSE;
2700            }
2701            return hasNext.booleanValue();
2702        }
2703
2704
2705        public void remove()
2706        {
2707            throw new UnsupportedOperationException JavaDoc(
2708                "'remove' is not implemented"); //EXCEPTION
2709
}
2710
2711
2712        int index = -1;
2713        /**
2714         * nextQueryResult should be non-null at the end of this method
2715         * if it returns true, otherwise false should be returned.
2716         *
2717         * @return a <code>boolean</code> value
2718         */

2719        private boolean prepareNextQueryResult()
2720        {
2721            boolean anyMoreResults;
2722            try
2723            {
2724                anyMoreResults = doPrepareNextQueryResult();
2725            }
2726            catch (Exception JavaDoc e)
2727            {
2728                anyMoreResults = false;
2729                Log.get(LOGGER).warn(
2730                    "An exception prevented getting the next result.", e);
2731            }
2732            return anyMoreResults;
2733        }
2734
2735        private boolean doPrepareNextQueryResult()
2736            throws SQLException JavaDoc
2737        {
2738            boolean anyMoreResults = true;
2739            
2740            if (index < 0 || index >= 1000)
2741            {
2742                if (cachedQRs == null)
2743                {
2744                    cachedQRs = new QueryResult[1000];
2745                }
2746                else
2747                {
2748                    // remove the old
2749
for (int i = cachedQRs.length - 1; i >= 0; i--)
2750                    {
2751                        cachedQRs[i] = null;
2752                    }
2753                    closeStatementsAndResultSets();
2754                }
2755                
2756                int count = 0;
2757                QueryResult qr;
2758                String JavaDoc previousPK = null;
2759                StringBuffer JavaDoc pks = new StringBuffer JavaDoc(512);
2760                while (count < 1000 && searchRS.next())
2761                {
2762                    String JavaDoc pk = searchRS.getString(1);
2763                    if (!pk.equals(previousPK))
2764                    {
2765                        previousPK = pk;
2766                        pks.append(pk).append(',');
2767                        qr = new QueryResult(search);
2768                        qr.setIssueId(pk);
2769                        qr.setIdPrefix(searchRS.getString(2));
2770                        qr.setIdCount(searchRS.getString(3));
2771                        qr.setModuleId(new Integer JavaDoc(searchRS.getInt(4)));
2772                        qr.setIssueTypeId(new Integer JavaDoc(searchRS.getInt(5)));
2773                        cachedQRs[count++] = qr;
2774                    }
2775                }
2776
2777                anyMoreResults = count > 0;
2778                if (anyMoreResults)
2779                {
2780                    index = 0;
2781                    pks.setLength(pks.length() - 1);
2782
2783                    // execute column result queries
2784
if (searchStuff.size() > 1)
2785                    {
2786                        Iterator JavaDoc i = searchStuff.iterator();
2787                        i.next();
2788                        while (i.hasNext())
2789                        {
2790                            ColumnBundle cb = (ColumnBundle)i.next();
2791                            StringBuffer JavaDoc sql = new StringBuffer JavaDoc(512);
2792                            sql.append(cb.select);
2793
2794                            sql.append(FROM).append(IssuePeer.TABLE_NAME);
2795                            if (cb.outerJoins != null)
2796                            {
2797                                sql.append(cb.outerJoins);
2798                            }
2799                            sql.append(WHERE).append(IssuePeer.ISSUE_ID)
2800                                .append(IN).append(pks).append(')');
2801                            addOrderByClause(sql, cb.sortColumn);
2802                            Statement JavaDoc stmt = conn.createStatement();
2803                            ResultSet JavaDoc rs = stmt.executeQuery(sql.toString());
2804                            rs.next();
2805                            stmtList.add(stmt);
2806                            rsList.add(new ResultSetAndSize(rs, cb.size));
2807                        }
2808                    }
2809                }
2810            }
2811            else if (cachedQRs[index] == null)
2812            {
2813                anyMoreResults = false;
2814            }
2815
2816            if (anyMoreResults)
2817            {
2818                // remove old results to allow gc, if needed
2819
if (index > 0)
2820                {
2821                    cachedQRs[index-1] = null;
2822                }
2823                nextQueryResult = cachedQRs[index++];
2824                buildQueryResult(nextQueryResult);
2825            }
2826
2827            return anyMoreResults;
2828        }
2829
2830
2831        /**
2832         * Assembles one or more rows from a <code>ResultSet</code> into a
2833         * single {@link QueryResult} object. Assumes that rows in the
2834         * <code>ResultSet</code> are grouped by issue.
2835         *
2836         * @return A single {@link QueryResult} object.
2837         * @exception SQLException If a database error occurs.
2838         */

2839        private void buildQueryResult(QueryResult qr)
2840            throws SQLException JavaDoc
2841        {
2842            String JavaDoc queryResultPK = qr.getIssueId();
2843            Logger scarabLog = Log.get("org.tigris.scarab");
2844
2845            // add column values
2846
int index = 0;
2847            for (Iterator JavaDoc iter = rsList.iterator(); iter.hasNext();)
2848            {
2849                ResultSetAndSize rss = (ResultSetAndSize)iter.next();
2850                ResultSet JavaDoc resultSet = rss.resultSet;
2851                int size = rss.size;
2852                String JavaDoc pk = resultSet.getString(1);
2853                while (queryResultPK.equals(pk))
2854                {
2855                    List JavaDoc values = qr.getAttributeValues();
2856                    // Each attribute can result in a separate record. As we
2857
// have sorted on the primary key column in addition to
2858
// any other sort, all attributes for a given issue will
2859
// be grouped. Map these multiple records into a single
2860
// QueryResult per issue.
2861
if (values == null || index >= values.size())
2862                    {
2863                        queryResultStarted(resultSet, qr, size);
2864                        if (scarabLog.isDebugEnabled())
2865                        {
2866                            scarabLog.debug("Fetching query result at index "
2867                                            + index + " with ID of "
2868                                            + queryResultPK);
2869                        }
2870                    }
2871                    else
2872                    {
2873                        queryResultContinued(resultSet, qr, index, size);
2874                    }
2875                    
2876                    pk = (resultSet.next()) ? resultSet.getString(1): null;
2877                }
2878                index += size;
2879            }
2880        }
2881
2882        private void queryResultStarted(ResultSet JavaDoc rs, QueryResult qr,
2883                                        int valueListSize)
2884            throws SQLException JavaDoc
2885        {
2886            if (valueListSize > 0)
2887            {
2888                // Some attributes can be multivalued.
2889
List JavaDoc values = new ArrayList JavaDoc(valueListSize);
2890                for (int j = 0; j < valueListSize; j++)
2891                {
2892                    ArrayList JavaDoc multiVal = new ArrayList JavaDoc(2);
2893                    multiVal.add(rs.getString(j + 4));
2894                    values.add(multiVal);
2895                }
2896                List JavaDoc lastValues = qr.getAttributeValues();
2897                if (lastValues == null)
2898                {
2899                    qr.setAttributeValues(values);
2900                }
2901                else
2902                {
2903                    lastValues.addAll(values);
2904                }
2905            }
2906        }
2907
2908        private void queryResultContinued(ResultSet JavaDoc rs, QueryResult qr,
2909                                          int base, int valueListSize)
2910            throws SQLException JavaDoc
2911        {
2912            if (valueListSize > 0)
2913            {
2914                List JavaDoc values = qr.getAttributeValues();
2915                for (int j = 0; j < valueListSize; j++)
2916                {
2917                    String JavaDoc s = rs.getString(j + 4);
2918
2919                    // As it's possible that multiple rows
2920
// could have the same value for a given
2921
// attribute, and we don't want to add the
2922
// same value many times, check for this
2923
// below. See the code in the "else if"
2924
// block about 10 lines down to see how
2925
// the values lists are arranged to allow
2926
// for multiple values.
2927
List JavaDoc prevValues = (List JavaDoc) values.get(j + base);
2928                    boolean newValue = true;
2929                    for (int k = 0; k < prevValues.size(); k++)
2930                    {
2931                        if (ObjectUtils.equals(prevValues.get(k), s))
2932                        {
2933                            newValue = false;
2934                            break;
2935                        }
2936                    }
2937                    if (newValue)
2938                    {
2939                        prevValues.add(s);
2940                    }
2941                }
2942            }
2943        }
2944    }
2945
2946    private static class ColumnBundle
2947    {
2948        int size;
2949        StringBuffer JavaDoc select;
2950        StringBuffer JavaDoc outerJoins;
2951        String JavaDoc sortColumn;
2952    }
2953
2954    private static class ResultSetAndSize
2955    {
2956        private ResultSet JavaDoc resultSet;
2957        private int size;
2958        ResultSetAndSize(ResultSet JavaDoc rs, int s)
2959        {
2960            resultSet = rs;
2961            size = s;
2962        }
2963    }
2964}
2965
2966
Popular Tags