Source Code Cross Referenced for IssueSearch.java in  » Issue-Tracking » scarab-0.21 » org » tigris » scarab » util » word » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Issue Tracking » scarab 0.21 » org.tigris.scarab.util.word 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        package org.tigris.scarab.util.word;
0002:
0003:        /* ================================================================
0004:         * Copyright (c) 2000-2002 CollabNet.  All rights reserved.
0005:         * 
0006:         * Redistribution and use in source and binary forms, with or without
0007:         * modification, are permitted provided that the following conditions are
0008:         * met:
0009:         * 
0010:         * 1. Redistributions of source code must retain the above copyright
0011:         * notice, this list of conditions and the following disclaimer.
0012:         * 
0013:         * 2. Redistributions in binary form must reproduce the above copyright
0014:         * notice, this list of conditions and the following disclaimer in the
0015:         * documentation and/or other materials provided with the distribution.
0016:         * 
0017:         * 3. The end-user documentation included with the redistribution, if
0018:         * any, must include the following acknowlegement: "This product includes
0019:         * software developed by Collab.Net <http://www.Collab.Net/>."
0020:         * Alternately, this acknowlegement may appear in the software itself, if
0021:         * and wherever such third-party acknowlegements normally appear.
0022:         * 
0023:         * 4. The hosted project names must not be used to endorse or promote
0024:         * products derived from this software without prior written
0025:         * permission. For written permission, please contact info@collab.net.
0026:         * 
0027:         * 5. Products derived from this software may not use the "Tigris" or 
0028:         * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without 
0029:         * prior written permission of Collab.Net.
0030:         * 
0031:         * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0032:         * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
0033:         * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0034:         * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
0035:         * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
0036:         * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
0037:         * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0038:         * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
0039:         * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
0040:         * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
0041:         * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0042:         *
0043:         * ====================================================================
0044:         * 
0045:         * This software consists of voluntary contributions made by many
0046:         * individuals on behalf of Collab.Net.
0047:         */
0048:
0049:        // JDK classes
0050:        import java.sql.Connection;
0051:        import java.sql.ResultSet;
0052:        import java.sql.SQLException;
0053:        import java.sql.Statement;
0054:        import java.text.DateFormat;
0055:        import java.text.ParseException;
0056:        import java.text.SimpleDateFormat;
0057:        import java.util.ArrayList;
0058:        import java.util.Date;
0059:        import java.util.HashMap;
0060:        import java.util.HashSet;
0061:        import java.util.Iterator;
0062:        import java.util.List;
0063:        import java.util.Locale;
0064:        import java.util.Map;
0065:        import java.util.NoSuchElementException;
0066:        import java.util.Set;
0067:
0068:        import org.apache.commons.collections.map.LRUMap;
0069:        import org.apache.commons.collections.map.LinkedMap;
0070:        import org.apache.commons.lang.ObjectUtils;
0071:        import org.apache.commons.lang.StringUtils;
0072:        import org.apache.fulcrum.localization.Localization;
0073:        import org.apache.fulcrum.parser.StringValueParser;
0074:        import org.apache.log4j.Logger;
0075:        import org.apache.torque.Torque;
0076:        import org.apache.torque.TorqueException;
0077:        import org.apache.torque.adapter.DB;
0078:        import org.apache.torque.om.ComboKey;
0079:        import org.apache.torque.om.ObjectKey;
0080:        import org.apache.torque.om.SimpleKey;
0081:        import org.apache.torque.util.Criteria;
0082:        import org.tigris.scarab.attribute.DateAttribute;
0083:        import org.tigris.scarab.attribute.OptionAttribute;
0084:        import org.tigris.scarab.attribute.StringAttribute;
0085:        import org.tigris.scarab.om.ActivityPeer;
0086:        import org.tigris.scarab.om.ActivitySetPeer;
0087:        import org.tigris.scarab.om.AttachmentTypePeer;
0088:        import org.tigris.scarab.om.Attribute;
0089:        import org.tigris.scarab.om.AttributeManager;
0090:        import org.tigris.scarab.om.AttributeValue;
0091:        import org.tigris.scarab.om.AttributeValuePeer;
0092:        import org.tigris.scarab.om.Issue;
0093:        import org.tigris.scarab.om.IssuePeer;
0094:        import org.tigris.scarab.om.IssueType;
0095:        import org.tigris.scarab.om.MITList;
0096:        import org.tigris.scarab.om.MITListItem;
0097:        import org.tigris.scarab.om.Module;
0098:        import org.tigris.scarab.om.ModuleManager;
0099:        import org.tigris.scarab.om.RModuleIssueType;
0100:        import org.tigris.scarab.om.RModuleIssueTypeManager;
0101:        import org.tigris.scarab.om.RModuleOption;
0102:        import org.tigris.scarab.om.RModuleOptionPeer;
0103:        import org.tigris.scarab.om.RModuleUserAttribute;
0104:        import org.tigris.scarab.om.ScarabUser;
0105:        import org.tigris.scarab.services.security.ScarabSecurity;
0106:        import org.tigris.scarab.tools.ScarabLocalizationTool;
0107:        import org.tigris.scarab.tools.localization.L10NKeySet;
0108:        import org.tigris.scarab.util.IteratorWithSize;
0109:        import org.tigris.scarab.util.Log;
0110:        import org.tigris.scarab.util.ScarabConstants;
0111:        import org.tigris.scarab.util.ScarabException;
0112:
0113:        /** 
0114:         * A utility class to build up and carry out a search for 
0115:         * similar issues.  It subclasses Issue for functionality, it is 
0116:         * not a more specific type of Issue.
0117:         *
0118:         * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
0119:         * @version $Id: IssueSearch.java 10382 2006-12-13 17:02:35Z dabbous $
0120:         */
0121:        public class IssueSearch extends Issue {
0122:            private static final int MAX_INNER_JOIN = ScarabConstants.QUERY_MAX_FILTER_CRITERIA;
0123:
0124:            private static final int MAX_JOIN = ScarabConstants.QUERY_MAX_JOIN;
0125:
0126:            public static final String ASC = "asc";
0127:            public static final String DESC = "desc";
0128:
0129:            public static final String CREATED_BY_KEY = "created_by";
0130:            public static final String ANY_KEY = "any";
0131:
0132:            // column names only
0133:            private static final String AV_OPTION_ID = AttributeValuePeer.OPTION_ID
0134:                    .substring(AttributeValuePeer.OPTION_ID.indexOf('.') + 1);
0135:            private static final String AV_ISSUE_ID = AttributeValuePeer.ISSUE_ID
0136:                    .substring(AttributeValuePeer.ISSUE_ID.indexOf('.') + 1);
0137:
0138:            private static final String ACTIVITYSETALIAS = "srchcobyactset";
0139:            private static final String ACTIVITYSETALIAS_MODIFICATION = "srchcobyactsetmodif";
0140:            private static final String ACTIVITYALIAS = "srchcobyact";
0141:
0142:            private static final String CREATED_BY = "CREATED_BY";
0143:            private static final String CREATED_DATE = "CREATED_DATE";
0144:            private static final String ATTRIBUTE_ID = "ATTRIBUTE_ID";
0145:            private static final String AND = " AND ";
0146:            private static final String OR = " OR ";
0147:            private static final String INNER_JOIN = " INNER JOIN ";
0148:            private static final String ON = " ON (";
0149:            private static final String IN = " IN (";
0150:            private static final String IS_NULL = " IS NULL";
0151:            private static final String LEFT_OUTER_JOIN = " LEFT OUTER JOIN ";
0152:            private static final String SELECT_DISTINCT = "select DISTINCT ";
0153:
0154:            private static final String ACTSET_TRAN_ID = ActivitySetPeer.TRANSACTION_ID
0155:                    .substring(ActivitySetPeer.TRANSACTION_ID.indexOf('.') + 1);
0156:            private static final String ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID = IssuePeer.CREATED_TRANS_ID
0157:                    + '=' + ACTIVITYSETALIAS + '.' + ACTSET_TRAN_ID;
0158:            private static final String ACTSET_CREATED_BY = ActivitySetPeer.CREATED_BY
0159:                    .substring(ActivitySetPeer.CREATED_BY.indexOf('.') + 1);
0160:            private static final String ACTSET_CREATED_DATE = ActivitySetPeer.CREATED_DATE
0161:                    .substring(ActivitySetPeer.CREATED_DATE.indexOf('.') + 1);
0162:            private static final String ACTSET_MODIFIED_BY = ActivitySetPeer.CREATED_BY
0163:                    .substring(ActivitySetPeer.CREATED_BY.indexOf('.') + 1)
0164:                    + " as MODIFIED_BY";
0165:            private static final String ACTSET_MODIFIED_DATE = ActivitySetPeer.CREATED_DATE
0166:                    .substring(ActivitySetPeer.CREATED_DATE.indexOf('.') + 1)
0167:                    + " as MODIFIED_DATE";
0168:            private static final String ACT_ISSUE_ID = ActivityPeer.ISSUE_ID
0169:                    .substring(ActivityPeer.ISSUE_ID.indexOf('.') + 1);
0170:            private static final String ACTIVITYALIAS_ISSUE_ID = ACTIVITYALIAS
0171:                    + '.' + ACT_ISSUE_ID;
0172:            private static final String ACTIVITYALIAS_ISSUE_ID__EQUALS__ISSUEPEER_ISSUE_ID = ACTIVITYALIAS_ISSUE_ID
0173:                    + '=' + IssuePeer.ISSUE_ID;
0174:            private static final String END_DATE = ActivityPeer.END_DATE
0175:                    .substring(ActivityPeer.END_DATE.indexOf('.') + 1);
0176:
0177:            private static final String AV_ATTR_ID = AttributeValuePeer.ATTRIBUTE_ID
0178:                    .substring(AttributeValuePeer.ATTRIBUTE_ID.indexOf('.') + 1);
0179:
0180:            private static final String ACT_NEW_USER_ID = ActivityPeer.NEW_USER_ID
0181:                    .substring(ActivityPeer.NEW_USER_ID.indexOf('.') + 1);
0182:            private static final String ACTIVITYALIAS_NEW_USER_ID = ACTIVITYALIAS
0183:                    + '.' + ACT_NEW_USER_ID;
0184:
0185:            private static final String WHERE = " WHERE ";
0186:            private static final String FROM = " FROM ";
0187:            private static final String ORDER_BY = " ORDER BY ";
0188:            private static final String BASE_OPTION_SORT_LEFT_JOIN = " LEFT OUTER JOIN "
0189:                    + RModuleOptionPeer.TABLE_NAME
0190:                    + " sortRMO ON "
0191:                    + '('
0192:                    + IssuePeer.MODULE_ID
0193:                    + "=sortRMO.MODULE_ID AND "
0194:                    + IssuePeer.TYPE_ID
0195:                    + "=sortRMO.ISSUE_TYPE_ID AND sortRMO.OPTION_ID=";
0196:            private static final String AV = "av";
0197:            private static final String DOT_OPTION_ID_PAREN = ".OPTION_ID)";
0198:            private static final String DOT_VALUE = ".VALUE";
0199:            private static final String SORTRMO_PREFERRED_ORDER = "sortRMO.PREFERRED_ORDER";
0200:
0201:            private static final Integer NUMBERKEY_0 = new Integer(0);
0202:
0203:            /**
0204:             * The managed database connection used while iterating over large
0205:             * query result sets using a cursor.  This connection <b>must</b>
0206:             * be explicitly closed when done with it (e.g. at the end of the
0207:             * request)!
0208:             */
0209:            private Connection conn;
0210:
0211:            /**
0212:             * The statement that will be used by the connection to get the issue
0213:             * ids for the query.
0214:             */
0215:            private Statement searchStmt;
0216:            /**
0217:             * The ResultSet(s) that contains the issue ids for a query.
0218:             */
0219:            private ResultSet searchRS;
0220:
0221:            /**
0222:             * The statement(s) that will be used by the connection to obtain the
0223:             * ResultSet for column data to be shown for the query.  Statements should
0224:             * be closed when no longer needed. Closing a Statement will release any 
0225:             * associated ResultSets for a compliant jdbc driver, but we don't rely 
0226:             * on this behavior.
0227:             */
0228:            private List stmtList;
0229:            /**
0230:             * The ResultSet(s) that contain query result column data.  We store the 
0231:             * size along with the RS in a ResultSetAndSize object, where size is
0232:             * the number of columns in the RS.
0233:             */
0234:            private List rsList;
0235:
0236:            /**
0237:             * used to track how long we hold the connection
0238:             */
0239:            private long connectionStartTime;
0240:
0241:            private SimpleDateFormat formatter;
0242:
0243:            private String searchWords;
0244:            private String commentQuery;
0245:            private Integer[] textScope;
0246:            private String minId;
0247:            private String maxId;
0248:            private String minDate;
0249:            private String maxDate;
0250:            private int minVotes;
0251:
0252:            private Integer stateChangeAttributeId;
0253:            private Integer stateChangeFromOptionId;
0254:            private Integer stateChangeToOptionId;
0255:            private String stateChangeFromDate;
0256:            private String stateChangeToDate;
0257:
0258:            /**
0259:             * sortAttributeId and sortInternalAttribute hold the attribute to be used
0260:             * for sorting. Only one of them must be setted.
0261:             */
0262:            private Integer sortAttributeId;
0263:            private String sortInternalAttribute;
0264:            private String sortPolarity;
0265:            private MITList mitList;
0266:
0267:            private List userIdList;
0268:            private List userSearchCriteriaList;
0269:            private List lastUsedAVList;
0270:            private boolean modified;
0271:
0272:            private int lastTotalIssueCount = -1;
0273:            private IteratorWithSize lastQueryResults = null;
0274:
0275:            // the attribute columns that will be shown
0276:            private List issueListAttributeColumns;
0277:
0278:            // used to cache a few modules and issuetypes to make listing
0279:            // a result set faster.
0280:            private LRUMap moduleMap = new LRUMap(20);
0281:            private LRUMap rmitMap = new LRUMap(20);
0282:
0283:            private boolean isSearchAllowed = true;
0284:
0285:            /** A counter of inner joins used in a query */
0286:            private int joinCounter;
0287:
0288:            /**
0289:             * This is the locale that the search is currently running
0290:             * under. We need it to parse the date attributes. It defaults
0291:             * to the US locale as that was the behaviour before.
0292:             * @todo Ideally, the minDate, maxDate and others should
0293:             * be Date objects, with the user of this class doing the
0294:             * parsing itself. However, the intake tool is currently
0295:             * configured to use this class directly. Hopefully when
0296:             * (if?) intake supports dates natively, we can drop the
0297:             * date parsing from this class and use Dates instead. 
0298:             */
0299:            private Locale locale = Locale.US;
0300:
0301:            private StringValueParser parser = null;
0302:
0303:            private ScarabLocalizationTool L10N = null;
0304:
0305:            private boolean isOperable = true;
0306:
0307:            IssueSearch(Issue issue, ScarabUser searcher) throws Exception {
0308:                this (issue.getModule(), issue.getIssueType(), searcher);
0309:
0310:                //
0311:                // Make copies of the issue's attribute values so that
0312:                // we can modify them later without affecting the issue
0313:                // itself.
0314:                //
0315:                // @todo: This section of code is a result of SCB965.
0316:                // However, I think a more significant problem is that
0317:                // ReportIssue is modifying the search's attribute values
0318:                // directly. I believe this breaks some OO principle or
0319:                // other and should be resolved some time.
0320:                //
0321:                List issueAttributes = issue.getAttributeValues();
0322:                List searchAttributes = this .getAttributeValues();
0323:
0324:                for (Iterator iter = issueAttributes.iterator(); iter.hasNext();) {
0325:                    AttributeValue value = (AttributeValue) iter.next();
0326:                    searchAttributes.add(value.copy());
0327:                }
0328:            }
0329:
0330:            IssueSearch(Module module, IssueType issueType, ScarabUser searcher)
0331:                    throws Exception {
0332:                super (module, issueType);
0333:                isSearchAllowed = searcher.hasPermission(
0334:                        ScarabSecurity.ISSUE__SEARCH, module);
0335:            }
0336:
0337:            IssueSearch(MITList mitList, ScarabUser searcher) throws Exception {
0338:                super ();
0339:                if (mitList == null || mitList.size() == 0) {
0340:                    throw new IllegalArgumentException(
0341:                            "A non-null list with at"
0342:                                    + " least one item is required."); //EXCEPTION
0343:                }
0344:
0345:                String[] perms = { ScarabSecurity.ISSUE__SEARCH };
0346:                MITList searchableList = mitList.getPermittedSublist(perms,
0347:                        searcher);
0348:
0349:                isSearchAllowed = searchableList.size() > 0;
0350:                isSearchAllowed = true;
0351:
0352:                if (searchableList.isSingleModuleIssueType()) {
0353:                    MITListItem item = searchableList.getFirstItem();
0354:                    setModuleId(item.getModuleId());
0355:                    setTypeId(item.getIssueTypeId());
0356:                } else {
0357:                    this .mitList = searchableList;
0358:                    if (searchableList.isSingleModule()) {
0359:                        setModule(searchableList.getModule());
0360:                    }
0361:                    if (searchableList.isSingleIssueType()) {
0362:                        setIssueType(searchableList.getIssueType());
0363:                    }
0364:                }
0365:            }
0366:
0367:            public boolean isOperable() {
0368:                return isOperable;
0369:            }
0370:
0371:            /**
0372:             * The issue can be flagged as inoperable. This can happen when
0373:             * a syntax check on the search criteria fails (e.g. invalid syntax on Date patterns)
0374:             * @param state
0375:             */
0376:            public void setIsOperable(boolean state) {
0377:                isOperable = state;
0378:            }
0379:
0380:            public Locale getLocale() {
0381:                return this .locale;
0382:            }
0383:
0384:            public void setLocale(Locale newLocale) {
0385:                this .locale = newLocale;
0386:            }
0387:
0388:            public boolean isXMITSearch() {
0389:                return mitList != null && !mitList.isSingleModuleIssueType();
0390:            }
0391:
0392:            /**
0393:             * List of attributes to show with each issue.
0394:             *
0395:             * @param rmuas a <code>List</code> of RModuleUserAttribute objects
0396:             */
0397:            public void setIssueListAttributeColumns(List rmuas) {
0398:                //FIXME! implement logic to determine if a new search is required.
0399:                //HELP: John, would it be sufficient to set modified=true?
0400:                issueListAttributeColumns = rmuas;
0401:            }
0402:
0403:            public List getIssueListAttributeColumns() {
0404:                return issueListAttributeColumns;
0405:            }
0406:
0407:            public List getUserIdList() {
0408:                return userIdList;
0409:            }
0410:
0411:            /**
0412:             * returns the list of attribute values
0413:             * for all attributes in the current module
0414:             * and defined for all currently searched issueTypes.
0415:             * @return
0416:             * @throws Exception
0417:             */
0418:            public LinkedMap getCommonAttributeValuesMap() throws Exception {
0419:                return internalGetAttributeValuesMap(true);
0420:            }
0421:
0422:            /**
0423:             * returns the list of attribute values
0424:             * for all attributes in the current module.
0425:             * @return
0426:             * @throws Exception
0427:             */
0428:            public LinkedMap getAllAvailableAttributeValuesMap()
0429:                    throws Exception {
0430:                return internalGetAttributeValuesMap(false);
0431:            }
0432:
0433:            /**
0434:             * returns the list of attribute values
0435:             * for all attributes in the current module.
0436:             * 
0437:             * if(commonOnly==true) 
0438:             * return only attribute values which are
0439:             * commonly defined for all searched issueTypes.
0440:             * @return
0441:             * @throws Exception
0442:             */
0443:            private LinkedMap internalGetAttributeValuesMap(boolean commonOnly)
0444:                    throws Exception {
0445:                LinkedMap result = null;
0446:                if (isXMITSearch()) {
0447:                    result = getMITAttributeValuesMap(commonOnly);
0448:                } else {
0449:                    result = super .getModuleAttributeValuesMap(false);
0450:                }
0451:                return result;
0452:            }
0453:
0454:            /**
0455:             * AttributeValues that are relevant to the issue's current module.
0456:             * Empty AttributeValues that are relevant for the module, but have 
0457:             * not been set for the issue are included.  The values are ordered
0458:             * according to the module's preference
0459:             */
0460:            private LinkedMap getMITAttributeValuesMap(boolean commonOnly)
0461:                    throws Exception {
0462:                LinkedMap result = null;
0463:
0464:                List attributes;
0465:                if (commonOnly) {
0466:                    attributes = mitList.getCommonAttributes(false);
0467:                } else {
0468:                    attributes = mitList.getAttributes(false, false);
0469:                }
0470:
0471:                Map siaValuesMap = getAttributeValuesMap();
0472:                if (attributes != null) {
0473:                    result = new LinkedMap((int) (1.25 * attributes.size() + 1));
0474:                    Iterator i = attributes.iterator();
0475:                    while (i.hasNext()) {
0476:                        Attribute attribute = (Attribute) i.next();
0477:                        String key = attribute.getName().toUpperCase();
0478:                        if (siaValuesMap.containsKey(key)) {
0479:                            result.put(key, siaValuesMap.get(key));
0480:                        } else {
0481:                            AttributeValue aval = AttributeValue
0482:                                    .getNewInstance(attribute, this );
0483:                            addAttributeValue(aval);
0484:                            result.put(key, aval);
0485:                        }
0486:                    }
0487:                }
0488:                return result;
0489:            }
0490:
0491:            /**
0492:             * @return The list of attributes of type "user" for the module(s)
0493:             * to search in.
0494:             */
0495:            public List getUserAttributes() throws Exception {
0496:                List result = null;
0497:                if (isXMITSearch()) {
0498:                    result = mitList.getCommonUserAttributes(false);
0499:                } else {
0500:                    result = getModule().getUserAttributes(getIssueType(),
0501:                            false);
0502:                }
0503:                return result;
0504:            }
0505:
0506:            public List getLeafRModuleOptions(Attribute attribute)
0507:                    throws Exception {
0508:                List result = null;
0509:                if (isXMITSearch()) {
0510:                    result = mitList.getCommonLeafRModuleOptions(attribute);
0511:                } else {
0512:                    result = getModule().getLeafRModuleOptions(attribute,
0513:                            getIssueType());
0514:                }
0515:                return result;
0516:            }
0517:
0518:            public List getCommonOptionTree(Attribute attribute)
0519:                    throws Exception {
0520:                return mitList.getCommonRModuleOptionTree(attribute);
0521:            }
0522:
0523:            public List getAllOptionTree(Attribute attribute) throws Exception {
0524:                return mitList.getAllRModuleOptionTree(attribute);
0525:            }
0526:
0527:            /**
0528:             * Get the words for which to search.
0529:             *
0530:             * @return Value of {@link #searchWords}.
0531:             */
0532:            public String getSearchWords() {
0533:                return searchWords;
0534:            }
0535:
0536:            /**
0537:             * Set the words for which to search.
0538:             *
0539:             * @param v Value to assign to {@link #searchWords}.
0540:             */
0541:            public void setSearchWords(String v) {
0542:                if (!ObjectUtils.equals(v, this .searchWords)) {
0543:                    modified = true;
0544:                    this .searchWords = v;
0545:                }
0546:            }
0547:
0548:            /**
0549:             * Get the value of commentQuery.
0550:             * @return value of commentQuery.
0551:             */
0552:            public String getCommentQuery() {
0553:                return commentQuery;
0554:            }
0555:
0556:            /**
0557:             * Set the value of commentQuery.
0558:             * @param v  Value to assign to commentQuery.
0559:             */
0560:            public void setCommentQuery(String v) {
0561:                if (!ObjectUtils.equals(v, this .commentQuery)) {
0562:                    modified = true;
0563:                    this .commentQuery = v;
0564:                }
0565:            }
0566:
0567:            /**
0568:             * Get the value of textScope.  if the scope is not set then all
0569:             * text attributes are returned.  if there are no relevant text
0570:             * attributes null will be returned.
0571:             * @return value of textScope.
0572:             */
0573:            public Integer[] getTextScope() throws Exception {
0574:                if (textScope == null) {
0575:                    textScope = getTextScopeForAll();
0576:                } else {
0577:                    for (int i = textScope.length - 1; i >= 0; i--) {
0578:                        if (NUMBERKEY_0.equals(textScope[i])) {
0579:                            textScope = getTextScopeForAll();
0580:                            break;
0581:                        }
0582:                    }
0583:                }
0584:                return textScope;
0585:            }
0586:
0587:            /**
0588:             * Sets the text search scope to all quick search text attributes.
0589:             */
0590:            private Integer[] getTextScopeForAll() throws Exception {
0591:                Integer[] textScope = null;
0592:                List textAttributes = getQuickSearchTextAttributeValues();
0593:                if (textAttributes != null) {
0594:                    textScope = new Integer[textAttributes.size()];
0595:                    for (int j = textAttributes.size() - 1; j >= 0; j--) {
0596:                        textScope[j] = ((AttributeValue) textAttributes.get(j))
0597:                                .getAttributeId();
0598:                    }
0599:                }
0600:                return textScope;
0601:            }
0602:
0603:            /**
0604:             * Set the value of textScope.
0605:             * @param v  Value to assign to textScope.
0606:             */
0607:            public void setTextScope(Integer[] v) throws Exception {
0608:                if (v != null) {
0609:                    for (int i = v.length - 1; i >= 0; i--) {
0610:                        if (v[i].equals(NUMBERKEY_0)) {
0611:                            v = getTextScopeForAll();
0612:                            break;
0613:                        }
0614:                    }
0615:                }
0616:
0617:                // note previous block may have made v == null though its not likely
0618:                // (don't replace the if with an else)
0619:                if (v == null) {
0620:                    modified |= this .textScope != null;
0621:                    this .textScope = null;
0622:                } else if (this .textScope != null
0623:                        && this .textScope.length == v.length) {
0624:                    for (int i = v.length - 1; i >= 0; i--) {
0625:                        if (!v[i].equals(this .textScope[i])) {
0626:                            modified = true;
0627:                            this .textScope = v;
0628:                            break;
0629:                        }
0630:                    }
0631:                } else {
0632:                    modified = true;
0633:                    this .textScope = v;
0634:                }
0635:            }
0636:
0637:            /**
0638:             * Get the value of minId.
0639:             * @return value of minId.
0640:             */
0641:            public String getMinId() {
0642:                return minId;
0643:            }
0644:
0645:            /**
0646:             * Set the value of minId.
0647:             * @param v  Value to assign to minId.
0648:             */
0649:            public void setMinId(String v) {
0650:                if (v != null && v.length() == 0) {
0651:                    v = null;
0652:                }
0653:                if (!ObjectUtils.equals(v, this .minId)) {
0654:                    modified = true;
0655:                    this .minId = v;
0656:                }
0657:            }
0658:
0659:            /**
0660:             * Get the value of maxId.
0661:             * @return value of maxId.
0662:             */
0663:            public String getMaxId() {
0664:                return maxId;
0665:            }
0666:
0667:            /**
0668:             * Set the value of maxId.
0669:             * @param v  Value to assign to maxId.
0670:             */
0671:            public void setMaxId(String v) {
0672:                if (v != null && v.length() == 0) {
0673:                    v = null;
0674:                }
0675:                if (!ObjectUtils.equals(v, this .maxId)) {
0676:                    modified = true;
0677:                    this .maxId = v;
0678:                }
0679:            }
0680:
0681:            /**
0682:             * Get the value of minDate.
0683:             * @return value of minDate.
0684:             */
0685:            public String getMinDate() {
0686:                return this .minDate;
0687:            }
0688:
0689:            /**
0690:             * Set the value of minDate.
0691:             * @param newMinDate  Value to assign to minDate.
0692:             */
0693:            public void setMinDate(String newMinDate) {
0694:                if (newMinDate != null && newMinDate.length() == 0) {
0695:                    newMinDate = null;
0696:                }
0697:
0698:                if (!ObjectUtils.equals(newMinDate, this .minDate)) {
0699:                    this .modified = true;
0700:                    this .minDate = newMinDate;
0701:                }
0702:            }
0703:
0704:            /**
0705:             * Get the value of maxDate.
0706:             * @return value of maxDate.
0707:             */
0708:            public String getMaxDate() {
0709:                return this .maxDate;
0710:            }
0711:
0712:            /**
0713:             * Set the value of maxDate.
0714:             * @param newMaxDate Value to assign to maxDate.
0715:             */
0716:            public void setMaxDate(String newMaxDate) {
0717:                if (newMaxDate != null && newMaxDate.length() == 0) {
0718:                    newMaxDate = null;
0719:                }
0720:
0721:                if (!ObjectUtils.equals(newMaxDate, this .maxDate)) {
0722:                    this .modified = true;
0723:                    this .maxDate = newMaxDate;
0724:                }
0725:            }
0726:
0727:            /**
0728:             * Get the value of minVotes.
0729:             * @return value of minVotes.
0730:             */
0731:            public int getMinVotes() {
0732:                return minVotes;
0733:            }
0734:
0735:            /**
0736:             * Set the value of minVotes.
0737:             * @param v  Value to assign to minVotes.
0738:             */
0739:            public void setMinVotes(int v) {
0740:                if (v != this .minVotes) {
0741:                    modified = true;
0742:                    this .minVotes = v;
0743:                }
0744:            }
0745:
0746:            /**
0747:             * Get the value of stateChangeAttributeId.
0748:             * @return value of stateChangeAttributeId.
0749:             */
0750:            public Integer getStateChangeAttributeId() {
0751:                return stateChangeAttributeId;
0752:            }
0753:
0754:            /**
0755:             * Set the value of stateChangeAttributeId.
0756:             * @param v  Value to assign to stateChangeAttributeId.
0757:             */
0758:            public void setStateChangeAttributeId(Integer v) {
0759:                if (!ObjectUtils.equals(v, this .stateChangeAttributeId)) {
0760:                    modified = true;
0761:                    this .stateChangeAttributeId = v;
0762:                }
0763:            }
0764:
0765:            /**
0766:             * Get the value of stateChangeFromOptionId.
0767:             * @return value of stateChangeFromOptionId.
0768:             */
0769:            public Integer getStateChangeFromOptionId() {
0770:                return stateChangeFromOptionId;
0771:            }
0772:
0773:            /**
0774:             * Set the value of stateChangeFromOptionId.
0775:             * @param v  Value to assign to stateChangeFromOptionId.
0776:             */
0777:            public void setStateChangeFromOptionId(Integer v) {
0778:                if (!ObjectUtils.equals(v, this .stateChangeFromOptionId)) {
0779:                    modified = true;
0780:                    this .stateChangeFromOptionId = v;
0781:                }
0782:            }
0783:
0784:            /**
0785:             * Get the value of stateChangeToOptionId.
0786:             * @return value of stateChangeToOptionId.
0787:             */
0788:            public Integer getStateChangeToOptionId() {
0789:                return stateChangeToOptionId;
0790:            }
0791:
0792:            /**
0793:             * Set the value of stateChangeToOptionId.
0794:             * @param v  Value to assign to stateChangeToOptionId.
0795:             */
0796:            public void setStateChangeToOptionId(Integer v) {
0797:                if (!ObjectUtils.equals(v, this .stateChangeToOptionId)) {
0798:                    modified = true;
0799:                    this .stateChangeToOptionId = v;
0800:                }
0801:            }
0802:
0803:            /**
0804:             * Get the value of stateChangeFromDate.
0805:             * @return value of stateChangeFromDate.
0806:             */
0807:            public String getStateChangeFromDate() {
0808:                return this .stateChangeFromDate;
0809:            }
0810:
0811:            /**
0812:             * Set the value of stateChangeFromDate.
0813:             * @param fromDate Value to assign to stateChangeFromDate.
0814:             */
0815:            public void setStateChangeFromDate(String fromDate) {
0816:                if (fromDate != null && fromDate.length() == 0) {
0817:                    fromDate = null;
0818:                }
0819:
0820:                if (!ObjectUtils.equals(fromDate, this .stateChangeFromDate)) {
0821:                    this .modified = true;
0822:                    this .stateChangeFromDate = fromDate;
0823:                }
0824:            }
0825:
0826:            /**
0827:             * Get the value of stateChangeToDate.
0828:             * @return value of stateChangeToDate.
0829:             */
0830:            public String getStateChangeToDate() {
0831:                return this .stateChangeToDate;
0832:            }
0833:
0834:            /**
0835:             * Set the value of stateChangeToDate.
0836:             * @param toDate Value to assign to stateChangeToDate.
0837:             */
0838:            public void setStateChangeToDate(String toDate) {
0839:                if (toDate != null && toDate.length() == 0) {
0840:                    toDate = null;
0841:                }
0842:
0843:                if (!ObjectUtils.equals(toDate, this .stateChangeToDate)) {
0844:                    this .modified = true;
0845:                    this .stateChangeToDate = toDate;
0846:                }
0847:            }
0848:
0849:            /**
0850:             * Get the value of sortAttributeId.
0851:             * @return value of SortAttributeId.
0852:             */
0853:            public Integer getSortAttributeId() {
0854:                return sortAttributeId;
0855:            }
0856:
0857:            /**
0858:             * Set the value of sortAttributeId.
0859:             * @param v  Value to assign to sortAttributeId.
0860:             */
0861:            public void setSortAttributeId(Integer v) {
0862:                if (!ObjectUtils.equals(v, this .sortAttributeId)) {
0863:                    modified = true;
0864:                    this .sortAttributeId = v;
0865:                }
0866:            }
0867:
0868:            public void setSortInternalAttribute(String internal) {
0869:                if (!ObjectUtils.equals(internal, this .sortInternalAttribute)) {
0870:                    modified = true;
0871:                    this .sortInternalAttribute = internal;
0872:                }
0873:            }
0874:
0875:            public String getSortInternalAttribute() {
0876:                return this .sortInternalAttribute;
0877:            }
0878:
0879:            /**
0880:             * Whether to do SQL sorting in <code>DESC</code> or
0881:             * <code>ASC</code> order (the default being the latter).
0882:             * @return value of sortPolarity.
0883:             */
0884:            public String getSortPolarity() {
0885:                return (DESC.equals(sortPolarity) ? DESC : ASC);
0886:            }
0887:
0888:            /**
0889:             * Set the value of sortPolarity.
0890:             * @param v  Value to assign to sortPolarity.
0891:             */
0892:            public void setSortPolarity(String v) {
0893:                if (!ObjectUtils.equals(v, this .sortPolarity)) {
0894:                    modified = true;
0895:                    this .sortPolarity = v;
0896:                }
0897:            }
0898:
0899:            /**
0900:             * Describe <code>addUserSearch</code> method here.
0901:             *
0902:             * @param userId a <code>String</code> represention of the PrimaryKey
0903:             * @param searchCriteria a <code>String</code> either a String 
0904:             * representation of an Attribute PrimaryKey, or the Strings "created_by" 
0905:             * "any"
0906:             */
0907:            public void addUserCriteria(String userId, String searchCriteria) {
0908:                if (userId == null) {
0909:                    throw new IllegalArgumentException("userId cannot be null."); //EXCEPTION
0910:                }
0911:                if (searchCriteria == null) {
0912:                    searchCriteria = ANY_KEY;
0913:                }
0914:
0915:                if (userIdList == null) {
0916:                    userIdList = new ArrayList(4);
0917:                    userSearchCriteriaList = new ArrayList(4);
0918:                }
0919:                boolean newCriteria = true;
0920:                for (int i = userIdList.size() - 1; i >= 0 && newCriteria; i--) {
0921:                    Object attrId = userSearchCriteriaList.get(i);
0922:                    // not new if attrId already present or an ANY search has already
0923:                    // been specified
0924:                    newCriteria = !(userId.equals(userIdList.get(i)) && (searchCriteria
0925:                            .equals(attrId) || ANY_KEY.equals(attrId)));
0926:                }
0927:
0928:                if (newCriteria) {
0929:                    modified = true;
0930:                    // if the new criteria is ANY, then remove old criteria 
0931:                    if (ANY_KEY.equals(searchCriteria)) {
0932:                        for (int i = userIdList.size() - 1; i >= 0; i--) {
0933:                            if (userId.equals(userIdList.get(i))) {
0934:                                userIdList.remove(i);
0935:                                userSearchCriteriaList.remove(i);
0936:                            }
0937:                        }
0938:                    }
0939:                    userIdList.add(userId);
0940:                    userSearchCriteriaList.add(searchCriteria);
0941:                }
0942:            }
0943:
0944:            private boolean isAVListModified() throws TorqueException {
0945:                boolean result = false;
0946:                if (lastUsedAVList == null) {
0947:                    result = true;
0948:                } else {
0949:                    List avList = getAttributeValues();
0950:                    int max = avList.size();
0951:                    if (lastUsedAVList.size() == max) {
0952:                        for (int i = 0; i < max; i++) {
0953:                            AttributeValue a1 = (AttributeValue) avList.get(i);
0954:                            AttributeValue a2 = (AttributeValue) lastUsedAVList
0955:                                    .get(i);
0956:                            if (!ObjectUtils.equals(a1.getOptionId(), a2
0957:                                    .getOptionId())
0958:                                    || !ObjectUtils.equals(a1.getUserId(), a2
0959:                                            .getUserId())
0960:                                    //|| a1.getNumericValue() != a2.getNumericValue()
0961:                                    || !ObjectUtils.equals(a1.getValue(), a2
0962:                                            .getValue())) {
0963:                                result = true;
0964:                            }
0965:                        }
0966:                    } else {
0967:                        result = true;
0968:                    }
0969:                }
0970:                return result;
0971:            }
0972:
0973:            /**
0974:             * 
0975:             *
0976:             * @return a <code>boolean</code> value
0977:             */
0978:            private void checkModified() throws TorqueException {
0979:                if (modified || isAVListModified()) {
0980:                    modified = false;
0981:                    lastTotalIssueCount = -1;
0982:                    lastQueryResults = null;
0983:                }
0984:            }
0985:
0986:            public Integer getALL_TEXT() {
0987:                return NUMBERKEY_0;
0988:            }
0989:
0990:            public List getQuickSearchTextAttributeValues() throws Exception {
0991:                return getTextAttributeValues(true);
0992:            }
0993:
0994:            public List getTextAttributeValues() throws Exception {
0995:                return getTextAttributeValues(false);
0996:            }
0997:
0998:            private List getTextAttributeValues(boolean quickSearchOnly)
0999:                    throws Exception {
1000:                LinkedMap searchValues = getCommonAttributeValuesMap();
1001:                List searchAttributes = new ArrayList(searchValues.size());
1002:
1003:                for (int i = 0; i < searchValues.size(); i++) {
1004:                    AttributeValue searchValue = (AttributeValue) searchValues
1005:                            .getValue(i);
1006:                    if ((!quickSearchOnly || searchValue
1007:                            .isQuickSearchAttribute())
1008:                            && searchValue.getAttribute().isTextAttribute()) {
1009:                        searchAttributes.add(searchValue);
1010:                    }
1011:                }
1012:
1013:                return searchAttributes;
1014:            }
1015:
1016:            /**
1017:             * Returns OptionAttributes which have been marked for Quick search.
1018:             *
1019:             * @return a <code>List</code> value
1020:             * @exception Exception if an error occurs
1021:             */
1022:            public List getQuickSearchOptionAttributeValues() throws Exception {
1023:                return getOptionAttributeValues(true);
1024:            }
1025:
1026:            /**
1027:             * Returns OptionAttributes which have been marked for Quick search.
1028:             *
1029:             * @return a <code>List</code> value
1030:             * @exception Exception if an error occurs
1031:             */
1032:            public List getOptionAttributeValues() throws Exception {
1033:                return getOptionAttributeValues(false);
1034:            }
1035:
1036:            /**
1037:             * Returns OptionAttributes which have been marked for Quick search.
1038:             *
1039:             * @return a <code>List</code> value
1040:             * @exception Exception if an error occurs
1041:             */
1042:            private List getOptionAttributeValues(boolean quickSearchOnly)
1043:                    throws Exception {
1044:                LinkedMap searchValues = getCommonAttributeValuesMap();
1045:                List searchAttributeValues = new ArrayList(searchValues.size());
1046:
1047:                for (int i = 0; i < searchValues.size(); i++) {
1048:                    AttributeValue searchValue = (AttributeValue) searchValues
1049:                            .getValue(i);
1050:                    if ((!quickSearchOnly || searchValue
1051:                            .isQuickSearchAttribute())
1052:                            && searchValue instanceof  OptionAttribute) {
1053:                        searchAttributeValues.add(searchValue);
1054:                    }
1055:                }
1056:
1057:                return searchAttributeValues;
1058:            }
1059:
1060:            /**
1061:             * remove unset AttributeValues.
1062:             *
1063:             * @param attValues a <code>List</code> value
1064:             */
1065:            private List removeUnsetValues(List attValues) {
1066:                int size = attValues.size();
1067:                List setAVs = new ArrayList(size);
1068:                for (int i = 0; i < size; i++) {
1069:                    AttributeValue attVal = (AttributeValue) attValues.get(i);
1070:                    if (attVal.isSet()) {
1071:                        setAVs.add(attVal);
1072:                    }
1073:                }
1074:                return setAVs;
1075:            }
1076:
1077:            private void addAnd(StringBuffer sb) {
1078:                if (sb.length() > 0) {
1079:                    sb.append(AND);
1080:                }
1081:            }
1082:
1083:            private void addIssueIdRange(StringBuffer where)
1084:                    throws ScarabException, Exception {
1085:                // check limits to see which ones are present
1086:                // if neither are present, do nothing
1087:                if ((minId != null && minId.length() != 0)
1088:                        || (maxId != null && maxId.length() != 0)) {
1089:                    StringBuffer sb = new StringBuffer();
1090:                    String domain = null;
1091:                    String prefix = null;
1092:                    Issue.FederatedId minFid = null;
1093:                    Issue.FederatedId maxFid = null;
1094:                    if (minId == null || minId.length() == 0) {
1095:                        maxFid = new Issue.FederatedId(maxId);
1096:                        setDefaults(null, maxFid);
1097:                        addAnd(sb);
1098:                        sb.append(IssuePeer.ID_COUNT).append("<=").append(
1099:                                maxFid.getCount());
1100:                        domain = maxFid.getDomain();
1101:                        prefix = maxFid.getPrefix();
1102:                    } else if (maxId == null || maxId.length() == 0) {
1103:                        minFid = new Issue.FederatedId(minId);
1104:                        setDefaults(minFid, null);
1105:                        addAnd(sb);
1106:                        sb.append(IssuePeer.ID_COUNT).append(">=").append(
1107:                                minFid.getCount());
1108:                        domain = minFid.getDomain();
1109:                        prefix = minFid.getPrefix();
1110:                    } else {
1111:                        minFid = new Issue.FederatedId(minId);
1112:                        maxFid = new Issue.FederatedId(maxId);
1113:                        setDefaults(minFid, maxFid);
1114:
1115:                        // make sure min id is less than max id and that the character
1116:                        // parts are equal otherwise skip the query, there are no 
1117:                        // matches
1118:                        if (minFid.getCount() <= maxFid.getCount()
1119:                                && StringUtils.equals(minFid.getPrefix(),
1120:                                        maxFid.getPrefix())
1121:                                && StringUtils.equals(minFid.getDomain(),
1122:                                        maxFid.getDomain())) {
1123:                            addAnd(sb);
1124:                            sb.append(IssuePeer.ID_COUNT).append(">=").append(
1125:                                    minFid.getCount()).append(AND).append(
1126:                                    IssuePeer.ID_COUNT).append("<=").append(
1127:                                    maxFid.getCount());
1128:                            domain = minFid.getDomain();
1129:                            prefix = minFid.getPrefix();
1130:                        } else {
1131:                            throw new ScarabException(
1132:                                    L10NKeySet.ExceptionIncompatibleIssueIds,
1133:                                    minId, maxId);
1134:                        }
1135:                    }
1136:                    if (domain != null) {
1137:                        sb.append(AND).append(IssuePeer.ID_DOMAIN).append("='")
1138:                                .append(domain).append('\'');
1139:                    }
1140:                    if (prefix != null) {
1141:                        sb.append(AND).append(IssuePeer.ID_PREFIX).append("='")
1142:                                .append(prefix).append('\'');
1143:                    }
1144:                    where.append(AND).append(sb);
1145:                }
1146:            }
1147:
1148:            /**
1149:             * give reasonable defaults if module code was not specified
1150:             */
1151:            private void setDefaults(FederatedId minFid, FederatedId maxFid)
1152:                    throws Exception {
1153:                Module module = getModule();
1154:                if (module != null) {
1155:                    if (minFid != null && minFid.getDomain() == null) {
1156:                        minFid.setDomain(module.getScarabInstanceId());
1157:                    }
1158:                    if (maxFid != null && maxFid.getDomain() == null) {
1159:                        maxFid.setDomain(module.getScarabInstanceId());
1160:                    }
1161:                    if (minFid != null && minFid.getPrefix() == null) {
1162:                        minFid.setPrefix(module.getCode());
1163:                    }
1164:                }
1165:                if (maxFid != null && maxFid.getPrefix() == null) {
1166:                    if (minFid == null) {
1167:                        maxFid.setPrefix(module.getCode());
1168:                    } else {
1169:                        maxFid.setPrefix(minFid.getPrefix());
1170:                    }
1171:                }
1172:            }
1173:
1174:            /**
1175:             * Attempts to parse a atring as a date, first using the locale-sepcific
1176:             * short date format, and then the ISO standard "yyyy-mm-dd". If it sees
1177:             * a ':' character in the date string then the string will be interpreted
1178:             * as a date <b>and</b> time. Throws a ParseException if the String does
1179:             * not contain a suitable format.
1180:             *
1181:             * @param dateString a <code>String</code> value
1182:             * @param locale the locale to use when determining the date patterns
1183:             * to try.
1184:             * @param addTwentyFourHours if no time is given in the date string and
1185:             * this flag is true, then 24 hours - 1 msec will be added to the date.
1186:             * @return a <code>Date</code> value
1187:             */
1188:            public Date parseDate(String dateString, boolean addTwentyFourHours)
1189:                    throws ParseException {
1190:                Date date = null;
1191:                if (dateString != null) {
1192:                    if (dateString.indexOf(':') == -1) {
1193:                        //
1194:                        // First try to parse the date using the current
1195:                        // locale. If that doesn't work, then try the
1196:                        // ISO format.
1197:                        //
1198:                        String[] patterns = {
1199:                                Localization.getString(this .locale,
1200:                                        "ShortDatePattern"),
1201:                                ScarabConstants.ISO_DATE_PATTERN };
1202:                        date = parseDate(dateString, patterns);
1203:
1204:                        // one last try with the default locale format
1205:                        if (date == null) {
1206:                            //
1207:                            // If this fails, then we want the parse exception
1208:                            // to propogate. That's why we don't use
1209:                            // parseDateWithFormat() here.
1210:                            //
1211:                            date = DateFormat.getDateInstance().parse(
1212:                                    dateString);
1213:                        }
1214:
1215:                        // add 24 hours to max date so it is inclusive
1216:                        if (addTwentyFourHours) {
1217:                            date.setTime(date.getTime() + 86399999);
1218:                        }
1219:                    } else {
1220:                        //
1221:                        // First try to parse the date using the current
1222:                        // locale. If that doesn't work, then try the
1223:                        // ISO format.
1224:                        //
1225:                        String[] patterns = {
1226:                                Localization.getString(this .locale,
1227:                                        "ShortDateTimePattern"),
1228:                                ScarabConstants.ISO_DATETIME_PATTERN };
1229:                        date = parseDate(dateString, patterns);
1230:
1231:                        // one last try with the default locale format
1232:                        if (date == null) {
1233:                            date = DateFormat.getDateTimeInstance().parse(
1234:                                    dateString);
1235:                        }
1236:                    }
1237:                }
1238:
1239:                return date;
1240:            }
1241:
1242:            /**
1243:             * Attempts to parse a String as a Date, trying each pattern in
1244:             * turn until the string is successfully parsed or all patterns
1245:             * have been tried.
1246:             *
1247:             * @param s a <code>String</code> value that should be converted
1248:             * to a <code>Date</code>.
1249:             * @param patterns if no time is given in the date string and
1250:             * this flag is true, then 24 hours - 1 msec will be added to the date.
1251:             * @return the equivalent <code>Date</code> if the string could
1252:             * be parsed. 
1253:             * @throws ParseException if input String is null, or the string
1254:             * could not be parsed.
1255:             */
1256:            private Date parseDate(String s, String[] patterns)
1257:                    throws ParseException {
1258:                /* FIXME: the contract for this method is strange
1259:                   it is returning a null value when encountering a ParseException,
1260:                   and throwing a ParseException when having a wrong input*/
1261:                if (s == null) {
1262:                    throw new ParseException("Input string was null", -1); //EXCEPTION
1263:                }
1264:
1265:                if (formatter == null) {
1266:                    formatter = new SimpleDateFormat();
1267:                }
1268:
1269:                for (int i = 0; i < patterns.length; i++) {
1270:                    formatter.applyPattern(patterns[i]);
1271:                    Date date = parseDateWithFormat(s, formatter);
1272:
1273:                    if (date != null) {
1274:                        return date;
1275:                    }
1276:                }
1277:
1278:                throw new ParseException("Date could not be parsed with any"
1279:                        + " of the provided date patterns.", -1); //EXCEPTION
1280:            }
1281:
1282:            private Date parseDateWithFormat(String dateString,
1283:                    DateFormat format) {
1284:                try {
1285:                    return format.parse(dateString);
1286:                } catch (ParseException ex) {
1287:                    return null;
1288:                }
1289:            }
1290:
1291:            private void addDateRange(String column, Date minUtilDate,
1292:                    Date maxUtilDate, StringBuffer sb) throws Exception {
1293:                // check limits to see which ones are present
1294:                // if neither are present, do nothing
1295:                if (minUtilDate != null || maxUtilDate != null) {
1296:                    DB adapter = Torque.getDB(Torque.getDefaultDB());
1297:                    if (minUtilDate == null) {
1298:                        sb.append(column).append('<').append(
1299:                                adapter.getDateString(maxUtilDate));
1300:                    } else if (maxUtilDate == null) {
1301:                        sb.append(column).append(">=").append(
1302:                                adapter.getDateString(minUtilDate));
1303:                    } else {
1304:                        // make sure min id is less than max id and that the character
1305:                        // parts are equal otherwise skip the query, there are no 
1306:                        // matches
1307:                        if (minUtilDate.before(maxUtilDate)) {
1308:                            sb.append(column).append(">=").append(
1309:                                    adapter.getDateString(minUtilDate));
1310:                            sb.append(AND);
1311:                            sb.append(column).append('<').append(
1312:                                    adapter.getDateString(maxUtilDate));
1313:                        } else {
1314:                            throw new ScarabException(
1315:                                    L10NKeySet.ExceptionMaxdateBeforeMindate,
1316:                                    this .maxDate, minUtilDate);
1317:                        }
1318:                    }
1319:                }
1320:            }
1321:
1322:            /**
1323:             * Returns a List of matching issues.  if no OptionAttributes were
1324:             * found in the input list, criteria is unaltered.
1325:             *
1326:             * @param attValues a <code>List</code> value
1327:             */
1328:            private void addSelectedAttributes(StringBuffer fromClause,
1329:                    StringBuffer whereClause, List attValues, Set tableAliases)
1330:                    throws Exception {
1331:                Map attrMap = new HashMap((int) (attValues.size() * 1.25));
1332:                for (int j = 0; j < attValues.size(); j++) {
1333:                    AttributeValue multiAV = (AttributeValue) attValues.get(j);
1334:                    if (multiAV instanceof  OptionAttribute) {
1335:                        Integer index = multiAV.getAttributeId();
1336:                        List options = (List) attrMap.get(index);
1337:                        if (options == null) {
1338:                            options = new ArrayList();
1339:                            attrMap.put(index, options);
1340:                        }
1341:
1342:                        //pull any chained values out to create a flat list
1343:                        List chainedValues = multiAV.getValueList();
1344:                        for (int i = 0; i < chainedValues.size(); i++) {
1345:                            AttributeValue aval = (AttributeValue) chainedValues
1346:                                    .get(i);
1347:                            Integer optionId = aval.getOptionId();
1348:                            if (optionId == null) {
1349:                                continue;
1350:                            }
1351:                            if (optionId.intValue() != 0) // Empty value is 0
1352:                            {
1353:                                buildOptionList(options, aval);
1354:                            } else {
1355:                                options.add(optionId);
1356:                            }
1357:
1358:                        }
1359:                    }
1360:                }
1361:
1362:                for (Iterator i = attrMap.entrySet().iterator(); i.hasNext();) {
1363:                    Map.Entry entry = (Map.Entry) i.next();
1364:                    String alias = "av" + entry.getKey();
1365:                    Integer key = (Integer) entry.getKey();
1366:                    String c2 = null;
1367:                    c2 = alias + '.' + AV_ATTR_ID + '=' + key;
1368:                    joinCounter++;
1369:                    String joinClause = LEFT_OUTER_JOIN
1370:                            + AttributeValuePeer.TABLE_NAME + ' ' + alias
1371:                            + " ON (" + alias + '.' + AV_ISSUE_ID + '='
1372:                            + IssuePeer.ISSUE_ID + AND + c2 + AND + alias + '.'
1373:                            + "DELETED=0" + ')';
1374:                    if (whereClause.length() > 0) {
1375:                        whereClause.append(AND);
1376:                    }
1377:                    whereClause.append('(');
1378:                    List options = (List) entry.getValue();
1379:                    boolean bSearched = false;
1380:                    if (options.size() == 1
1381:                            && !(((Integer) options.get(0)).intValue() == 0)) {
1382:                        whereClause.append(alias + '.' + AV_OPTION_ID + '='
1383:                                + options.get(0));
1384:                        bSearched = true;
1385:                    }
1386:                    if (options.size() > 1) {
1387:                        whereClause.append(alias + '.' + AV_OPTION_ID + " IN ("
1388:                                + StringUtils.join(options.iterator(), ",")
1389:                                + ')');
1390:                        bSearched = true;
1391:                    }
1392:                    if (options.contains(new Integer(0))) //is 'empty' option selected?
1393:                    {
1394:                        if (bSearched) {
1395:                            whereClause.append(OR);
1396:                        }
1397:                        whereClause.append(alias + '.' + AV_OPTION_ID
1398:                                + " IS NULL");
1399:                    }
1400:                    whereClause.append(')');
1401:                    fromClause.append(joinClause);
1402:                    tableAliases.add(alias);
1403:                }
1404:            }
1405:
1406:            /**
1407:             * <p>This method builds a Criterion for a single attribute value.
1408:             * It is used in the addOptionAttributes method.</p>
1409:             * <p>The attribute value is basically the attribute name/id
1410:             * + its value. Since some option (picklist) attributes are
1411:             * hierachical, we need to add any child values of the given
1412:             * attribute value. For example, assume we have an attribute named
1413:             * "Operating System". This might have values in a hierarchy like
1414:             * so:</p>
1415:             * <pre>
1416:             *   All
1417:             *     Windows
1418:             *       NT
1419:             *       2000
1420:             *       XP
1421:             *     Unix
1422:             *       Linux
1423:             *       Solaris
1424:             *       Tru64
1425:             * </pre>
1426:             * <p>If the user selects the "Windows" value in a query, we want
1427:             * to include any issues that have "Windows" as this attribute's
1428:             * value, and also "NT", "2000", and "XP".</p>
1429:             * <p>All the appropriate attribute values are added to the 'options'
1430:             * list as RModuleOption objects.</p>
1431:             *
1432:             * @param aval an <code>AttributeValue</code> value
1433:             * @return a <code>Criteria.Criterion</code> value
1434:             */
1435:            private void buildOptionList(List options, AttributeValue aval)
1436:                    throws Exception {
1437:                List descendants = null;
1438:                // it would be a more correct query to separate the descendant
1439:                // options by module and do something like
1440:                // ... (module_id=1 and option_id in (1,2,3)) OR (module_id=5...
1441:                // but we are not checking which options are active here so i
1442:                // don't think the complexity of the query is needed.  might want
1443:                // to revisit, especially the part about ignoring active setting.
1444:                if (isXMITSearch()) {
1445:                    descendants = mitList.getDescendantsUnion(aval
1446:                            .getAttributeOption());
1447:                } else {
1448:                    IssueType issueType = getIssueType();
1449:
1450:                    //
1451:                    // This call checks whether the attribute value is available
1452:                    // to the current module. If not, then no attribute options
1453:                    // are added to the list.
1454:                    //
1455:                    RModuleOption rmo = getModule().getRModuleOption(
1456:                            aval.getAttributeOption(), issueType);
1457:                    if (rmo != null) {
1458:                        descendants = rmo.getDescendants(issueType);
1459:                    }
1460:                }
1461:
1462:                //
1463:                // Include the selected attribute value as one of the options
1464:                // to search for.
1465:                //
1466:                options.add(aval.getOptionId());
1467:
1468:                if (descendants != null && !descendants.isEmpty()) {
1469:                    //
1470:                    // Add all applicable child attribute options to the list as well.
1471:                    //
1472:                    for (Iterator i = descendants.iterator(); i.hasNext();) {
1473:                        options.add(((RModuleOption) i.next()).getOptionId());
1474:                    }
1475:                }
1476:            }
1477:
1478:            private void addUserAndCreatedDateCriteria(StringBuffer from,
1479:                    StringBuffer where) throws Exception {
1480:                String dateRangeSql = null;
1481:                if (getMinDate() != null || getMaxDate() != null) {
1482:                    StringBuffer sbdate = new StringBuffer();
1483:                    Date minUtilDate = parseDate(getMinDate(), false);
1484:                    Date maxUtilDate = parseDate(getMaxDate(), true);
1485:                    addDateRange(ACTIVITYSETALIAS + '.' + CREATED_DATE,
1486:                            minUtilDate, maxUtilDate, sbdate);
1487:                    dateRangeSql = sbdate.toString();
1488:                }
1489:
1490:                if (userIdList == null || userIdList.isEmpty()) {
1491:                    if (dateRangeSql != null) {
1492:                        joinCounter++;
1493:                        // just dates
1494:                        from
1495:                                .append(INNER_JOIN)
1496:                                .append(ActivitySetPeer.TABLE_NAME)
1497:                                .append(' ')
1498:                                .append(ACTIVITYSETALIAS)
1499:                                .append(ON)
1500:                                .append(
1501:                                        ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID)
1502:                                .append(AND).append(dateRangeSql).append(')');
1503:                    }
1504:                } else {
1505:                    List anyUsers = null;
1506:                    List creatorUsers = null;
1507:                    Map attrUsers = null;
1508:
1509:                    int maxUsers = userIdList.size();
1510:                    // separate users by attribute, Created_by, and Any
1511:                    for (int i = 0; i < maxUsers; i++) {
1512:                        String userId = (String) userIdList.get(i);
1513:                        String attrId = (String) userSearchCriteriaList.get(i);
1514:                        if (attrId == null || ANY_KEY.equals(attrId)) {
1515:                            if (anyUsers == null) {
1516:                                anyUsers = new ArrayList(maxUsers);
1517:                            }
1518:                            anyUsers.add(userId);
1519:                        } else if (CREATED_BY_KEY.equals(attrId)) {
1520:                            if (creatorUsers == null) {
1521:                                creatorUsers = new ArrayList(maxUsers);
1522:                            }
1523:                            creatorUsers.add(userId);
1524:                        } else {
1525:                            // using a map here seems like overkill, but it
1526:                            // makes the logic easier
1527:                            if (attrUsers == null) {
1528:                                attrUsers = new HashMap(maxUsers);
1529:                            }
1530:                            List userIds = (List) attrUsers.get(attrId);
1531:                            if (userIds == null) {
1532:                                userIds = new ArrayList(maxUsers);
1533:                                attrUsers.put(attrId, userIds);
1534:                            }
1535:                            userIds.add(userId);
1536:                        }
1537:                    }
1538:
1539:                    // All users are compared using OR, so use a single alias
1540:                    // for activities related to users.
1541:                    joinCounter++;
1542:                    StringBuffer fromClause = new StringBuffer(100);
1543:                    fromClause.append(INNER_JOIN).append(
1544:                            ActivityPeer.TABLE_NAME).append(' ').append(
1545:                            ACTIVITYALIAS).append(ON).append(
1546:                            ACTIVITYALIAS_ISSUE_ID__EQUALS__ISSUEPEER_ISSUE_ID);
1547:
1548:                    StringBuffer attrCrit = null;
1549:                    if (anyUsers != null) {
1550:                        attrCrit = new StringBuffer(50);
1551:                        attrCrit.append('(');
1552:                        addUserActivityFragment(attrCrit, anyUsers);
1553:                        attrCrit.append(')');
1554:                    }
1555:
1556:                    // Add sql fragment for each attribute.  The sql is similar
1557:                    // to the one used for Any users with the addition of attribute 
1558:                    // criteria
1559:                    if (attrUsers != null) {
1560:                        for (Iterator i = attrUsers.entrySet().iterator(); i
1561:                                .hasNext();) {
1562:                            if (attrCrit == null) {
1563:                                attrCrit = new StringBuffer();
1564:                            } else {
1565:                                attrCrit.append(OR);
1566:                            }
1567:
1568:                            Map.Entry entry = (Map.Entry) i.next();
1569:                            String attrId = (String) entry.getKey();
1570:                            List userIds = (List) entry.getValue();
1571:                            attrCrit.append('(');
1572:                            addUserActivityFragment(attrCrit, userIds);
1573:                            attrCrit.append(AND + ACTIVITYALIAS + '.'
1574:                                    + ATTRIBUTE_ID + '=' + attrId);
1575:                            attrCrit.append(')');
1576:                        }
1577:                    }
1578:
1579:                    boolean isAddActivitySet = anyUsers != null
1580:                            || creatorUsers != null || dateRangeSql != null;
1581:                    String whereClause = null;
1582:                    if (isAddActivitySet) {
1583:                        if (attrCrit != null) {
1584:                            whereClause = '(' + attrCrit.toString() + ')';
1585:                        }
1586:
1587:                        joinCounter++;
1588:                        fromClause
1589:                                .append(')')
1590:                                .append(INNER_JOIN)
1591:                                .append(ActivitySetPeer.TABLE_NAME)
1592:                                .append(' ')
1593:                                .append(ACTIVITYSETALIAS)
1594:                                .append(ON)
1595:                                .append(
1596:                                        ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID);
1597:
1598:                        if (anyUsers != null || creatorUsers != null) {
1599:                            List anyAndCreators = new ArrayList(maxUsers);
1600:                            if (anyUsers != null) {
1601:                                anyAndCreators.addAll(anyUsers);
1602:                            }
1603:                            if (creatorUsers != null) {
1604:                                anyAndCreators.addAll(creatorUsers);
1605:                            }
1606:
1607:                            // we can add this to the join condition, if created-only
1608:                            // query otherwise it needs to go in the where clause
1609:                            String createdBySqlFragment = ACTIVITYSETALIAS
1610:                                    + '.' + CREATED_BY;
1611:                            if (anyAndCreators.size() == 1) {
1612:                                createdBySqlFragment += '=' + anyAndCreators
1613:                                        .get(0).toString();
1614:                            } else {
1615:                                createdBySqlFragment += IN
1616:                                        + StringUtils.join(anyAndCreators
1617:                                                .iterator(), ",") + ')';
1618:                            }
1619:
1620:                            if (anyUsers != null || attrUsers != null) {
1621:                                fromClause.append(')');
1622:                                whereClause = '(' + whereClause + OR
1623:                                        + createdBySqlFragment + ')';
1624:                                if (dateRangeSql != null) {
1625:                                    whereClause += AND + dateRangeSql;
1626:                                }
1627:                            } else {
1628:                                fromClause.append(AND).append(
1629:                                        createdBySqlFragment);
1630:                                if (dateRangeSql != null) {
1631:                                    fromClause.append(AND).append(dateRangeSql);
1632:                                }
1633:                                fromClause.append(')');
1634:                            }
1635:                        } else // dateRangeSql will not be null
1636:                        {
1637:                            fromClause.append(AND).append(dateRangeSql).append(
1638:                                    ')');
1639:                        }
1640:                    } else {
1641:                        // we only had single-attribute users and no date criteria.
1642:                        // attrCrit will not be null, because we had to have at
1643:                        // least one user or we'd not be here
1644:                        fromClause.append(AND).append('(').append(attrCrit)
1645:                                .append("))");
1646:                    }
1647:
1648:                    from.append(fromClause.toString());
1649:                    if (whereClause != null) {
1650:                        where.append(AND).append(whereClause);
1651:                    }
1652:                }
1653:            }
1654:
1655:            private void addUserActivityFragment(StringBuffer sb, List userIds) {
1656:                sb.append(ACTIVITYALIAS + '.' + END_DATE + IS_NULL + AND
1657:                        + ACTIVITYALIAS_NEW_USER_ID);
1658:                if (userIds.size() == 1) {
1659:                    sb.append('=').append(userIds.get(0));
1660:                } else {
1661:                    sb.append(IN + StringUtils.join(userIds.iterator(), ",")
1662:                            + ')');
1663:                }
1664:            }
1665:
1666:            private Long[] getTextMatches(List attValues,
1667:                    boolean mergeTextResults) throws Exception {
1668:                boolean searchCriteriaExists = false;
1669:                Long[] matchingIssueIds = null;
1670:                SearchIndex searchIndex = SearchFactory.getInstance();
1671:                if (searchIndex == null) {
1672:                    // Check your configuration.
1673:                    throw new Exception("No index available to search"); //EXCEPTION
1674:                }
1675:                if (getSearchWords() != null && getSearchWords().length() != 0) {
1676:                    searchIndex.addQuery(getTextScope(), getSearchWords());
1677:                    searchCriteriaExists = true;
1678:                } else {
1679:                    for (int i = 0; i < attValues.size(); i++) {
1680:                        AttributeValue aval = (AttributeValue) attValues.get(i);
1681:                        if (aval.getValue() != null
1682:                                && aval.getValue().length() != 0) {
1683:                            /** Parser will only be != null if we are "searching" issues **/
1684:                            if (parser != null && aval instanceof  DateAttribute) {
1685:                                String auxDate = parser.getString("attv__"
1686:                                        + aval.getAttributeId().intValue()
1687:                                        + "val_aux");
1688:                                if (auxDate == null)
1689:                                    auxDate = "";
1690:                                else
1691:                                    auxDate = DateAttribute
1692:                                            .internalDateFormat(
1693:                                                    auxDate.trim(),
1694:                                                    L10N
1695:                                                            .get(L10NKeySet.ShortDatePattern));
1696:                                aval.setValue(DateAttribute.internalDateFormat(
1697:                                        aval.getValue(),
1698:                                        L10N.get(L10NKeySet.ShortDatePattern)));
1699:                                searchCriteriaExists = true;
1700:                                Integer[] id = { aval.getAttributeId() };
1701:                                if (auxDate.equals(aval.getValue()))
1702:                                    searchIndex.addQuery(id, aval.getValue());
1703:                                else
1704:                                    searchIndex.addQuery(id, SearchIndex.TEXT
1705:                                            + ":[" + aval.getValue() + " TO "
1706:                                            + auxDate + "]");
1707:                            } else if (aval instanceof  StringAttribute) {
1708:                                searchCriteriaExists = true;
1709:                                Integer[] id = { aval.getAttributeId() };
1710:                                searchIndex.addQuery(id, aval.getValue());
1711:                            }
1712:                        }
1713:
1714:                    }
1715:                }
1716:
1717:                // add comment attachments
1718:                String commentQuery = getCommentQuery();
1719:                if (commentQuery != null && commentQuery.trim().length() > 0) {
1720:                    Integer[] id = { AttachmentTypePeer.COMMENT_PK };
1721:                    searchIndex.addAttachmentQuery(id, commentQuery);
1722:                    searchCriteriaExists = true;
1723:                }
1724:
1725:                if (searchCriteriaExists) {
1726:                    try {
1727:                        matchingIssueIds = searchIndex
1728:                                .getRelatedIssues(mergeTextResults);
1729:                    } catch (Exception e) {
1730:                        SearchFactory.releaseInstance(searchIndex);
1731:                        throw e;
1732:                    }
1733:                }
1734:
1735:                SearchFactory.releaseInstance(searchIndex);
1736:                return matchingIssueIds;
1737:
1738:            }
1739:
1740:            private void addStateChangeQuery(StringBuffer from)
1741:                    throws Exception {
1742:                Integer oldOptionId = getStateChangeFromOptionId();
1743:                Integer newOptionId = getStateChangeToOptionId();
1744:                Date minUtilDate = parseDate(getStateChangeFromDate(), false);
1745:                Date maxUtilDate = parseDate(getStateChangeToDate(), true);
1746:                if ((oldOptionId != null && !oldOptionId.equals(NUMBERKEY_0))
1747:                        || (newOptionId != null && !newOptionId
1748:                                .equals(NUMBERKEY_0)) || minUtilDate != null
1749:                        || maxUtilDate != null) {
1750:                    joinCounter++;
1751:                    from.append(INNER_JOIN + ActivityPeer.TABLE_NAME + ON
1752:                            + ActivityPeer.ISSUE_ID + '=' + IssuePeer.ISSUE_ID);
1753:
1754:                    if (oldOptionId == null && newOptionId == null) {
1755:                        from.append(AND).append(ActivityPeer.ATTRIBUTE_ID)
1756:                                .append('=')
1757:                                .append(getStateChangeAttributeId());
1758:                    } else {
1759:                        if (newOptionId != null
1760:                                && !newOptionId.equals(NUMBERKEY_0)) {
1761:                            from.append(AND).append(ActivityPeer.NEW_OPTION_ID)
1762:                                    .append('=').append(newOptionId);
1763:                        }
1764:                        if (oldOptionId != null
1765:                                && !oldOptionId.equals(NUMBERKEY_0)) {
1766:                            from.append(AND).append(ActivityPeer.OLD_OPTION_ID)
1767:                                    .append('=').append(oldOptionId);
1768:                        }
1769:                    }
1770:                    from.append(')');
1771:
1772:                    // add dates, if given
1773:                    if (minUtilDate != null || maxUtilDate != null) {
1774:                        joinCounter++;
1775:                        from.append(INNER_JOIN + ActivitySetPeer.TABLE_NAME
1776:                                + ON + ActivitySetPeer.TRANSACTION_ID + '='
1777:                                + ActivityPeer.TRANSACTION_ID);
1778:                        from.append(AND);
1779:
1780:                        addDateRange(ActivitySetPeer.CREATED_DATE, minUtilDate,
1781:                                maxUtilDate, from);
1782:
1783:                        from.append(')');
1784:                    }
1785:                }
1786:            }
1787:
1788:            private Long[] addCoreSearchCriteria(StringBuffer fromClause,
1789:                    StringBuffer whereClause, Set tableAliases,
1790:                    boolean mergePartialQueryResults) throws Exception {
1791:                if (isXMITSearch()) {
1792:                    Criteria crit = new Criteria();
1793:                    mitList.addToCriteria(crit);
1794:                    String sql = crit.toString();
1795:                    int wherePos = sql.indexOf(" WHERE ");
1796:                    String where = sql.substring(wherePos + 7);
1797:                    whereClause.append(where);
1798:                } else {
1799:                    whereClause.append(IssuePeer.MODULE_ID).append('=').append(
1800:                            getModule().getModuleId());
1801:                    whereClause.append(AND).append(IssuePeer.TYPE_ID).append(
1802:                            '=').append(getIssueType().getIssueTypeId());
1803:                }
1804:                whereClause.append(AND).append(IssuePeer.DELETED).append("=0");
1805:                whereClause.append(AND).append(IssuePeer.MOVED).append("=0");
1806:
1807:                // add option values
1808:                lastUsedAVList = new ArrayList(getAttributeValues());
1809:
1810:                // remove unset AttributeValues before searching
1811:                List setAttValues = removeUnsetValues(lastUsedAVList);
1812:                addSelectedAttributes(fromClause, whereClause, setAttValues,
1813:                        tableAliases);
1814:
1815:                // search for issues based on text
1816:                Long[] matchingIssueIds = getTextMatches(setAttValues,
1817:                        mergePartialQueryResults);
1818:
1819:                if (matchingIssueIds == null || matchingIssueIds.length > 0) {
1820:                    addIssueIdRange(whereClause);
1821:                    //addMinimumVotes(whereClause);
1822:
1823:                    // add user values
1824:                    addUserAndCreatedDateCriteria(fromClause, whereClause);
1825:
1826:                    // add text search matches
1827:                    addIssuePKsCriteria(whereClause, matchingIssueIds);
1828:
1829:                    // state change query
1830:                    addStateChangeQuery(fromClause);
1831:                }
1832:                return matchingIssueIds;
1833:            }
1834:
1835:            private void addIssuePKsCriteria(StringBuffer sb, Long[] ids) {
1836:                if (ids != null && ids.length > 0) {
1837:                    sb.append(AND).append(IssuePeer.ISSUE_ID).append(IN)
1838:                            .append(StringUtils.join(ids, ",")).append(')');
1839:                }
1840:            }
1841:
1842:            /**
1843:             * Get a List of Issues that match the criteria given by this
1844:             * SearchIssue's searchWords and the quick search attribute values.
1845:             * Perform a logical AND on partial queries (in text searches)
1846:             * @return a <code>List</code> value
1847:             * @exception Exception if an error occurs
1848:             */
1849:            public IteratorWithSize getQueryResults()
1850:                    throws ComplexQueryException, Exception {
1851:                return getQueryResults(false);
1852:            }
1853:
1854:            /**
1855:             * Get a List of Issues that match the criteria given by this
1856:             * SearchIssue's searchWords and the quick search attribute values.
1857:             * if (mergePartialQueries==true) perform a logical OR on partial queries,
1858:             * otherwise perform a logical AND on partial queries.
1859:             * @return a <code>List</code> value
1860:             * @exception Exception if an error occurs
1861:             */
1862:            public IteratorWithSize getQueryResults(boolean mergePartialQueries)
1863:                    throws ComplexQueryException, Exception {
1864:                checkModified();
1865:                if (!isSearchAllowed) {
1866:                    lastQueryResults = IteratorWithSize.EMPTY;
1867:                } else if (lastQueryResults == null) {
1868:                    Set tableAliases = new HashSet();
1869:                    StringBuffer from = new StringBuffer();
1870:                    StringBuffer where = new StringBuffer();
1871:                    joinCounter = 0;
1872:                    Long[] matchingIssueIds = addCoreSearchCriteria(from,
1873:                            where, tableAliases, mergePartialQueries);
1874:                    if (joinCounter > MAX_INNER_JOIN) {
1875:                        //WORK [HD} Need refactoring here. How can a user
1876:                        //          create too complex queries ?
1877:                        throw new ComplexQueryException(
1878:                                L10NKeySet.ExceptionQueryTooComplex);
1879:                    }
1880:                    // the matchingIssueIds are text search matches.  if length == 0,
1881:                    // then no need to search further.  if null then there was no
1882:                    // text to search, so continue the search process.
1883:                    if (matchingIssueIds == null || matchingIssueIds.length > 0) {
1884:                        lastQueryResults = getQueryResults(from, where,
1885:                                tableAliases, mergePartialQueries);
1886:                    } else {
1887:                        lastQueryResults = IteratorWithSize.EMPTY;
1888:                    }
1889:                }
1890:
1891:                return lastQueryResults;
1892:            }
1893:
1894:            public int getIssueCount() throws ComplexQueryException, Exception {
1895:
1896:                return getIssueCount(false);
1897:            }
1898:
1899:            public int getIssueCount(boolean mergePartialQueries)
1900:                    throws ComplexQueryException, Exception {
1901:                checkModified();
1902:                int count = 0;
1903:                if (isSearchAllowed) {
1904:                    if (lastTotalIssueCount >= 0) {
1905:                        count = lastTotalIssueCount;
1906:                    } else {
1907:                        count = countFromDB(mergePartialQueries);
1908:                    }
1909:                    lastTotalIssueCount = count;
1910:                }
1911:
1912:                return count;
1913:            }
1914:
1915:            private int countFromDB(boolean mergePartialQueries)
1916:                    throws ComplexQueryException, Exception {
1917:                int count = 0;
1918:                StringBuffer from = new StringBuffer();
1919:                StringBuffer where = new StringBuffer();
1920:                joinCounter = 0;
1921:                Long[] matchingIssueIds = addCoreSearchCriteria(from, where,
1922:                        new HashSet(), mergePartialQueries);
1923:                if (joinCounter > MAX_INNER_JOIN) {
1924:                    //WORK [HD} Need refactoring here. How can a user
1925:                    //          create too complex queries ?
1926:                    //[JRG] The answer is: Putting values in too much attribute-lists,
1927:                    //      because for every one, an inner join will be created.
1928:                    //      So, new question; How much is *too* complex?
1929:                    //      Should we get this limit highere?
1930:                    //
1931:                    throw new ComplexQueryException(
1932:                            L10NKeySet.ExceptionQueryTooComplex);
1933:                }
1934:
1935:                if (matchingIssueIds == null || matchingIssueIds.length > 0) {
1936:                    StringBuffer sql = new StringBuffer(
1937:                            "SELECT count(DISTINCT ");
1938:                    sql.append(IssuePeer.ISSUE_ID).append(')').append(" FROM ")
1939:                            .append(IssuePeer.TABLE_NAME);
1940:                    if (from.length() > 0) {
1941:                        sql.append(' ').append(from.toString());
1942:                    }
1943:                    if (where.length() > 0) {
1944:                        sql.append(WHERE).append(where.toString());
1945:                    }
1946:                    String countSql = sql.toString();
1947:
1948:                    Connection localCon = conn;
1949:                    Statement stmt = null;
1950:                    try {
1951:                        if (localCon == null) {
1952:                            localCon = Torque.getConnection();
1953:                        }
1954:                        long startTime = System.currentTimeMillis();
1955:                        stmt = localCon.createStatement();
1956:                        ResultSet resultSet = stmt.executeQuery(countSql);
1957:                        if (resultSet.next()) {
1958:                            count = resultSet.getInt(1);
1959:                        }
1960:                        logTime(countSql, System.currentTimeMillis()
1961:                                - startTime, 50L, 500L);
1962:                    } finally {
1963:                        if (stmt != null) {
1964:                            stmt.close();
1965:                        }
1966:                        if (conn == null && localCon != null) {
1967:                            localCon.close();
1968:                        }
1969:                    }
1970:                }
1971:                return count;
1972:            }
1973:
1974:            private static final String LOGGER = "org.apache.torque";
1975:
1976:            private void logTime(String message, long time, long infoLimit,
1977:                    long warnLimit) {
1978:                if (time > warnLimit) {
1979:                    Log.get(LOGGER).warn(message + "\nTime = " + time + " ms");
1980:                } else if (time > infoLimit) {
1981:                    Logger log = Log.get(LOGGER);
1982:                    if (log.isInfoEnabled()) {
1983:                        log.info(message + "\nTime = " + time + " ms");
1984:                    }
1985:                } else {
1986:                    Logger log = Log.get(LOGGER);
1987:                    if (log.isDebugEnabled()) {
1988:                        log.info(message + "\nTime = " + time + " ms");
1989:                    }
1990:                }
1991:            }
1992:
1993:            private String setupSortColumn(Integer sortAttrId,
1994:                    StringBuffer sortOuterJoin, Set tableAliases)
1995:                    throws TorqueException {
1996:                String alias = AV + sortAttrId;
1997:                if (!tableAliases.contains(alias)) {
1998:                    sortOuterJoin.append(LEFT_OUTER_JOIN).append(
1999:                            AttributeValuePeer.TABLE_NAME).append(' ').append(
2000:                            alias).append(ON).append(IssuePeer.ISSUE_ID)
2001:                            .append('=').append(alias).append(".ISSUE_ID AND ")
2002:                            .append(alias).append(".DELETED=0 AND ").append(
2003:                                    alias).append(".ATTRIBUTE_ID=").append(
2004:                                    sortAttrId).append(')');
2005:                }
2006:                String sortColumn;
2007:                Attribute att = AttributeManager.getInstance(sortAttrId);
2008:                if (att.isOptionAttribute()) {
2009:                    // add the sort column
2010:                    sortColumn = SORTRMO_PREFERRED_ORDER;
2011:                    // join the RMO table to the alias we are sorting
2012:                    sortOuterJoin.append(BASE_OPTION_SORT_LEFT_JOIN).append(
2013:                            alias).append(DOT_OPTION_ID_PAREN);
2014:                } else {
2015:                    sortColumn = alias + DOT_VALUE;
2016:                }
2017:                return sortColumn;
2018:            }
2019:
2020:            private String setupInternalSortColumn(String sortInternal,
2021:                    StringBuffer sortOuterJoin, Set tableAliases)
2022:                    throws TorqueException {
2023:                String sortColumn = null;
2024:                String joinColumn = null;
2025:                String alias = ACTIVITYSETALIAS + "_sort";
2026:                if (sortInternal.equals(RModuleUserAttribute.MODIFIED_BY
2027:                        .getName())) {
2028:                    sortColumn = ACTSET_CREATED_BY;
2029:                    joinColumn = IssuePeer.LAST_TRANS_ID;
2030:                } else if (sortInternal
2031:                        .equals(RModuleUserAttribute.MODIFIED_DATE.getName())) {
2032:                    sortColumn = ACTSET_CREATED_DATE;
2033:                    joinColumn = IssuePeer.LAST_TRANS_ID;
2034:                } else if (sortInternal.equals(RModuleUserAttribute.CREATED_BY
2035:                        .getName())) {
2036:                    sortColumn = ACTSET_CREATED_BY;
2037:                    joinColumn = IssuePeer.CREATED_TRANS_ID;
2038:                } else if (sortInternal
2039:                        .equals(RModuleUserAttribute.CREATED_DATE.getName())) {
2040:                    sortColumn = ACTSET_CREATED_DATE;
2041:                    joinColumn = IssuePeer.CREATED_TRANS_ID;
2042:                }
2043:                sortOuterJoin.append(LEFT_OUTER_JOIN).append(
2044:                        ActivitySetPeer.TABLE_NAME).append(' ').append(alias)
2045:                        .append(ON).append(alias).append(".TRANSACTION_ID ")
2046:                        .append('=').append(joinColumn).append(')');
2047:
2048:                return alias + "." + sortColumn;
2049:            }
2050:
2051:            private List getSearchSqlPieces(StringBuffer from,
2052:                    StringBuffer where, Set tableAliases)
2053:                    throws TorqueException {
2054:                List searchStuff = new ArrayList(3);
2055:                Integer sortAttrId = getSortAttributeId();
2056:                String sortInternal = getSortInternalAttribute();
2057:
2058:                // Get matching issues, with sort criteria
2059:                StringBuffer sql = getSelectStart();
2060:                // WARNING!! The order of this fields is important!!
2061:                // SEE: doPrepareNextQueryResult in this class!!
2062:                sql.append(',').append(IssuePeer.MODULE_ID).append(',').append(
2063:                        IssuePeer.TYPE_ID).append(',').append(ACTIVITYSETALIAS)
2064:                        .append('.').append(ACTSET_CREATED_BY).append(',')
2065:                        .append(ACTIVITYSETALIAS).append('.').append(
2066:                                ACTSET_CREATED_DATE).append(',').append(
2067:                                ACTIVITYSETALIAS_MODIFICATION).append('.')
2068:                        .append(ACTSET_MODIFIED_BY).append(',').append(
2069:                                ACTIVITYSETALIAS_MODIFICATION).append('.')
2070:                        .append(ACTSET_MODIFIED_DATE);
2071:                String sortColumn = null;
2072:                StringBuffer sortOuterJoin = null;
2073:                if (sortAttrId != null) {
2074:                    sortOuterJoin = new StringBuffer(128);
2075:                    sortColumn = setupSortColumn(sortAttrId, sortOuterJoin,
2076:                            tableAliases);
2077:                    sql.append(',').append(sortColumn);
2078:                } else if (sortInternal != null) {
2079:                    sortOuterJoin = new StringBuffer(128);
2080:                    sortColumn = setupInternalSortColumn(sortInternal,
2081:                            sortOuterJoin, tableAliases);
2082:                    sql.append(',').append(sortColumn);
2083:                }
2084:
2085:                sql.append(FROM).append(IssuePeer.TABLE_NAME);
2086:
2087:                // add the join clause if not already exists
2088:                String joinAliasPart = ' ' + ACTIVITYSETALIAS + ON;
2089:                if (from.lastIndexOf(joinAliasPart) == -1) {
2090:                    sql.append(INNER_JOIN).append(ActivitySetPeer.TABLE_NAME);
2091:                    sql.append(joinAliasPart);
2092:                    sql.append(IssuePeer.CREATED_TRANS_ID).append('=');
2093:                    sql.append(ACTIVITYSETALIAS).append('.').append(
2094:                            ACTSET_TRAN_ID).append(')');
2095:                }
2096:
2097:                // add the join clause if not already exists
2098:                joinAliasPart = ' ' + ACTIVITYSETALIAS_MODIFICATION + ON;
2099:                if (from.lastIndexOf(joinAliasPart) == -1) {
2100:                    sql.append(LEFT_OUTER_JOIN).append(
2101:                            ActivitySetPeer.TABLE_NAME);
2102:                    sql.append(joinAliasPart);
2103:                    sql.append(IssuePeer.LAST_TRANS_ID).append('=');
2104:                    sql.append(ACTIVITYSETALIAS_MODIFICATION).append('.')
2105:                            .append(ACTSET_TRAN_ID).append(')');
2106:                }
2107:
2108:                if (from.length() > 0) {
2109:                    sql.append(' ').append(from.toString());
2110:                }
2111:                if (sortOuterJoin != null) {
2112:                    sql.append(sortOuterJoin);
2113:                }
2114:                if (where.length() > 0) {
2115:                    sql.append(WHERE).append(where.toString());
2116:                }
2117:                addOrderByClause(sql, sortColumn);
2118:                searchStuff.add(sql.toString());
2119:
2120:                // add the attribute value columns that will be shown in the list.
2121:                // these are joined using a left outer join, so the additional
2122:                // columns do not affect the results of the search (no additional
2123:                // criteria are added to the where clause.)          
2124:                List rmuas = getIssueListAttributeColumns();
2125:                if (rmuas != null) {
2126:                    int valueListSize = rmuas.size();
2127:                    StringBuffer outerJoin = new StringBuffer(
2128:                            10 * valueListSize + 20);
2129:
2130:                    int count = 0;
2131:                    int maxJoin = MAX_JOIN - 2;
2132:                    //List columnSqlList = new ArrayList(valueListSize/maxJoin + 1);
2133:                    StringBuffer partialSql = getSelectStart();
2134:                    tableAliases = new HashSet(MAX_JOIN);
2135:                    for (Iterator i = rmuas.iterator(); i.hasNext();) {
2136:                        RModuleUserAttribute rmua = (RModuleUserAttribute) i
2137:                                .next();
2138:                        Integer attrPK = rmua.getAttributeId();
2139:
2140:                        String id = attrPK.toString();
2141:                        String alias = AV + id;
2142:                        // add column to SELECT column clause
2143:                        partialSql.append(',').append(alias).append(DOT_VALUE);
2144:                        // if no criteria was specified for a displayed attribute
2145:                        // add it as an outer join
2146:                        if (!tableAliases.contains(alias)
2147:                                && !attrPK.equals(sortAttrId)) {
2148:                            outerJoin.append(LEFT_OUTER_JOIN).append(
2149:                                    AttributeValuePeer.TABLE_NAME).append(' ')
2150:                                    .append(alias).append(ON).append(
2151:                                            IssuePeer.ISSUE_ID).append('=')
2152:                                    .append(alias).append(".ISSUE_ID AND ")
2153:                                    .append(alias).append(".DELETED=0 AND ")
2154:                                    .append(alias).append(".ATTRIBUTE_ID=")
2155:                                    .append(id).append(')');
2156:                            tableAliases.add(alias);
2157:                        }
2158:
2159:                        count++;
2160:                        if (count == maxJoin || !i.hasNext()) {
2161:                            ColumnBundle cb = new ColumnBundle();
2162:                            cb.size = count;
2163:                            if (sortAttrId != null) {
2164:                                cb.sortColumn = setupSortColumn(sortAttrId,
2165:                                        outerJoin, tableAliases);
2166:                                partialSql.append(',').append(cb.sortColumn);
2167:                            } else if (sortInternal != null) {
2168:                                cb.sortColumn = setupInternalSortColumn(
2169:                                        sortInternal, outerJoin, tableAliases);
2170:                                partialSql.append(',').append(cb.sortColumn);
2171:                            }
2172:                            cb.select = partialSql;
2173:                            cb.outerJoins = outerJoin;
2174:                            searchStuff.add(cb);
2175:
2176:                            partialSql = getSelectStart();
2177:                            outerJoin = new StringBuffer(512);
2178:                            tableAliases.clear();
2179:                            count = 0;
2180:                        }
2181:                    }
2182:                }
2183:                return searchStuff;
2184:            }
2185:
2186:            private IteratorWithSize getQueryResults(StringBuffer from,
2187:                    StringBuffer where, Set tableAliases,
2188:                    boolean mergePartialQueries) throws TorqueException,
2189:                    ComplexQueryException, Exception {
2190:                // return a List of QueryResult objects
2191:                IteratorWithSize result = null;
2192:                try {
2193:                    if (conn == null) {
2194:                        conn = Torque.getConnection();
2195:                        connectionStartTime = System.currentTimeMillis();
2196:                    }
2197:
2198:                    // The code currently always calls getIssueCount() after we run this
2199:                    // query.  We can avoid having to use 2 connections by calling it
2200:                    // now using 'conn'.  It leaves the possibility of running the two
2201:                    // queries within a transaction as well.  We also use the result 
2202:                    // here to avoid the more complex query if there are no results.
2203:                    int count = getIssueCount(mergePartialQueries);
2204:                    if (count > 0) {
2205:                        result = new QueryResultIterator(this , count, from,
2206:                                where, tableAliases);
2207:                    } else {
2208:                        result = IteratorWithSize.EMPTY;
2209:                    }
2210:                } catch (SQLException e) {
2211:                    close();
2212:                    throw e; //EXCEPTION
2213:                }
2214:                /*
2215:                catch (TorqueException e)
2216:                {
2217:                    close();
2218:                    throw e;
2219:                }
2220:                 */
2221:                return result;
2222:            }
2223:
2224:            private void addOrderByClause(StringBuffer sql, String sortColumn) {
2225:                if (sortColumn == null) {
2226:                    sql.append(ORDER_BY).append(IssuePeer.ID_PREFIX);
2227:                    sql.append(' ').append(getSortPolarity());
2228:                    sql.append(',').append(IssuePeer.ID_COUNT);
2229:                    sql.append(' ').append(getSortPolarity());
2230:                } else {
2231:                    sql.append(ORDER_BY).append(sortColumn);
2232:                    sql.append(' ').append(getSortPolarity());
2233:                    // add pk sort so that rows can be combined easily
2234:                    sql.append(',').append(IssuePeer.ISSUE_ID).append(" ASC");
2235:                }
2236:            }
2237:
2238:            private StringBuffer getSelectStart() {
2239:                StringBuffer sql = new StringBuffer(512);
2240:                sql.append(SELECT_DISTINCT).append(IssuePeer.ISSUE_ID).append(
2241:                        ',').append(IssuePeer.ID_PREFIX).append(',').append(
2242:                        IssuePeer.ID_COUNT);
2243:                return sql;
2244:            }
2245:
2246:            /**
2247:             * Used by QueryResult to avoid multiple db hits in the event caching
2248:             * is not being used application-wide.  It is used if the IssueList.vm
2249:             * template is printing the module names next to each issue id.
2250:             * As this IssueSearch object is short-lived, use of a simple Map based
2251:             * cache is ok, need to re-examine if the lifespan is increased.
2252:             *
2253:             * @param id an <code>Integer</code> value
2254:             * @return a <code>Module</code> value
2255:             * @exception TorqueException if an error occurs
2256:             */
2257:            Module getModule(Integer id) throws TorqueException {
2258:                Module module = (Module) moduleMap.get(id);
2259:                if (module == null) {
2260:                    module = ModuleManager.getInstance(id);
2261:                    moduleMap.put(id, module);
2262:                }
2263:                return module;
2264:            }
2265:
2266:            /**
2267:             * Used by QueryResult to avoid multiple db hits in the event caching
2268:             * is not being used application-wide.  It is used if the IssueList.vm
2269:             * template is printing the issue type names next to each issue id.
2270:             * As this IssueSearch object is short-lived, use of a simple Map based
2271:             * cache is ok, need to re-examine if the lifespan is increased.
2272:             *
2273:             * @param moduleId an <code>Integer</code> value
2274:             * @param issueTypeId an <code>Integer</code> value
2275:             * @return a <code>RModuleIssueType</code> value
2276:             * @exception TorqueException if an error occurs
2277:             */
2278:            RModuleIssueType getRModuleIssueType(Integer moduleId,
2279:                    Integer issueTypeId) throws TorqueException {
2280:                SimpleKey[] nks = { SimpleKey.keyFor(moduleId.intValue()),
2281:                        SimpleKey.keyFor(issueTypeId.intValue()) };
2282:                ObjectKey key = new ComboKey(nks);
2283:                RModuleIssueType rmit = (RModuleIssueType) rmitMap.get(key);
2284:                if (rmit == null) {
2285:                    rmit = RModuleIssueTypeManager.getInstance(key);
2286:                    rmitMap.put(key, rmit);
2287:                }
2288:                return rmit;
2289:            }
2290:
2291:            /**
2292:             * Called by the garbage collector to release any database
2293:             * resources associated with this query.
2294:             *
2295:             * @see #close()
2296:             */
2297:            protected void finalize() throws Throwable {
2298:                try {
2299:                    if (conn != null) {
2300:                        Log.get(LOGGER).warn(
2301:                                "Closing connection in " + this  + " finalizer");
2302:                        // if this object was left this state it is very likely that
2303:                        // the IssueSearchFactory was not notified either.
2304:                        // We error on the side of possibly increasing the available
2305:                        // IssueSearch objects, over potentially freezing users out
2306:                        IssueSearchFactory.INSTANCE.notifyDone();
2307:                    }
2308:                } finally {
2309:                    close();
2310:                    super .finalize();
2311:                }
2312:            }
2313:
2314:            /**
2315:             * Releases any managed resources associated with this search
2316:             * (e.g. database connections, etc.).
2317:             */
2318:            public void close() {
2319:                if (conn != null) {
2320:                    // Be extremely paranoid about assuring that the database
2321:                    // connection is released to avoid leaks.
2322:                    try {
2323:                        Logger log = Log.get(LOGGER);
2324:                        if (log.isDebugEnabled()) {
2325:                            log
2326:                                    .debug("Releasing issue search database connection");
2327:                        }
2328:                    } finally {
2329:                        try {
2330:                            if (searchRS != null) {
2331:                                searchRS.close();
2332:                                searchRS = null;
2333:                            }
2334:                        } catch (Exception e) {
2335:                            try {
2336:                                Log.get(LOGGER).warn(
2337:                                        "Unable to close jdbc Statement", e);
2338:                            } catch (Exception ignore) {
2339:                            }
2340:                        }
2341:                        try {
2342:                            if (searchStmt != null) {
2343:                                searchStmt.close();
2344:                                searchStmt = null;
2345:                            }
2346:                        } catch (Exception e) {
2347:                            try {
2348:                                Log.get(LOGGER).warn(
2349:                                        "Unable to close jdbc Statement", e);
2350:                            } catch (Exception ignore) {
2351:                            }
2352:                        }
2353:
2354:                        try {
2355:                            closeStatementsAndResultSets();
2356:                            stmtList = null;
2357:                            rsList = null;
2358:                        } catch (Exception e) {
2359:                            try {
2360:                                Log.get(LOGGER).warn(
2361:                                        "Unable to close jdbc Statement", e);
2362:                            } catch (Exception ignore) {
2363:                            }
2364:                        }
2365:
2366:                        Torque.closeConnection(this .conn);
2367:                        this .conn = null;
2368:                        logTime(
2369:                                this 
2370:                                        + " released database connection which was held for:",
2371:                                System.currentTimeMillis()
2372:                                        - connectionStartTime, 5000L, 30000L);
2373:                    }
2374:                }
2375:            }
2376:
2377:            private void closeStatementsAndResultSets() throws SQLException {
2378:                if (rsList != null) {
2379:                    for (Iterator iter = rsList.iterator(); iter.hasNext();) {
2380:                        ((ResultSetAndSize) iter.next()).resultSet.close();
2381:                    }
2382:                    rsList.clear();
2383:                }
2384:
2385:                if (stmtList != null) {
2386:                    for (Iterator iter = stmtList.iterator(); iter.hasNext();) {
2387:                        ((Statement) iter.next()).close();
2388:                    }
2389:                    stmtList.clear();
2390:                }
2391:            }
2392:
2393:            /**
2394:             * The query currently being launched, so we can access any value from it.
2395:             * @param query
2396:             */
2397:            public void setQuery(String query) throws Exception {
2398:                this .parser = new StringValueParser();
2399:                parser.parse(query, '&', '=', true);
2400:            }
2401:
2402:            /**
2403:             * Allows setting the L10N tool for using when is needed to know
2404:             * the user's locale (example, when parsing date parameters) 
2405:             * @param l10nTool
2406:             */
2407:            public void setLocalizationTool(ScarabLocalizationTool l10nTool) {
2408:                this .L10N = l10nTool;
2409:            }
2410:
2411:            private class QueryResultIterator implements  IteratorWithSize {
2412:                final IssueSearch search;
2413:                final List searchStuff;
2414:                final int size;
2415:
2416:                QueryResult[] cachedQRs;
2417:
2418:                /**
2419:                 * @param issues The issue query results.
2420:                 */
2421:                private QueryResultIterator(IssueSearch search, int size,
2422:                        StringBuffer from, StringBuffer where, Set tableAliases)
2423:                        throws SQLException, TorqueException {
2424:                    this .search = search;
2425:                    this .size = size;
2426:                    searchStuff = getSearchSqlPieces(from, where, tableAliases);
2427:
2428:                    int numQueries = searchStuff.size();
2429:                    stmtList = new ArrayList(numQueries);
2430:                    rsList = new ArrayList(numQueries);
2431:
2432:                    long queryStartTime = System.currentTimeMillis();
2433:                    searchStmt = conn.createStatement();
2434:                    String searchSql = (String) searchStuff.get(0);
2435:                    try {
2436:                        searchRS = searchStmt.executeQuery(searchSql);
2437:                        logTime(
2438:                                searchSql
2439:                                        + "\nTime to only execute the query, not return results.",
2440:                                System.currentTimeMillis() - queryStartTime,
2441:                                50L, 500L);
2442:                    } catch (SQLException e) {
2443:                        Log.get(LOGGER).warn(
2444:                                "Search sql:\n" + searchSql
2445:                                        + "\nresulted in an exception: "
2446:                                        + e.getMessage());
2447:                        throw e; //EXCEPTION
2448:
2449:                    }
2450:                }
2451:
2452:                public int size() {
2453:                    return size;
2454:                }
2455:
2456:                // ----------------------------------------------------------------
2457:                // Iterator implementation
2458:
2459:                private QueryResult nextQueryResult;
2460:
2461:                public Object next() {
2462:                    if (hasNext()) {
2463:                        hasNext = null;
2464:                        return nextQueryResult;
2465:                    } else {
2466:                        throw new NoSuchElementException(
2467:                                "Iterator is exhausted"); //EXCEPTION
2468:                    }
2469:                }
2470:
2471:                private Boolean hasNext;
2472:
2473:                public boolean hasNext() {
2474:                    if (hasNext == null) {
2475:                        hasNext = (prepareNextQueryResult()) ? Boolean.TRUE
2476:                                : Boolean.FALSE;
2477:                    }
2478:                    return hasNext.booleanValue();
2479:                }
2480:
2481:                public void remove() {
2482:                    throw new UnsupportedOperationException(
2483:                            "'remove' is not implemented"); //EXCEPTION
2484:                }
2485:
2486:                int index = -1;
2487:
2488:                /**
2489:                 * nextQueryResult should be non-null at the end of this method
2490:                 * if it returns true, otherwise false should be returned.
2491:                 *
2492:                 * @return a <code>boolean</code> value
2493:                 */
2494:                private boolean prepareNextQueryResult() {
2495:                    boolean anyMoreResults;
2496:                    try {
2497:                        anyMoreResults = doPrepareNextQueryResult();
2498:                    } catch (Exception e) {
2499:                        anyMoreResults = false;
2500:                        Log
2501:                                .get(LOGGER)
2502:                                .warn(
2503:                                        "An exception prevented getting the next result.",
2504:                                        e);
2505:                    }
2506:                    return anyMoreResults;
2507:                }
2508:
2509:                private boolean doPrepareNextQueryResult() throws SQLException {
2510:                    boolean anyMoreResults = true;
2511:
2512:                    if (index < 0 || index >= 1000) {
2513:                        if (cachedQRs == null) {
2514:                            cachedQRs = new QueryResult[1000];
2515:                        } else {
2516:                            // remove the old
2517:                            for (int i = cachedQRs.length - 1; i >= 0; i--) {
2518:                                cachedQRs[i] = null;
2519:                            }
2520:                            closeStatementsAndResultSets();
2521:                        }
2522:
2523:                        int count = 0;
2524:                        QueryResult qr;
2525:                        String previousPK = null;
2526:                        StringBuffer pks = new StringBuffer(512);
2527:                        while (count < 1000 && searchRS.next()) {
2528:                            String pk = searchRS.getString(1);
2529:                            if (!pk.equals(previousPK)) {
2530:                                previousPK = pk;
2531:                                pks.append(pk).append(',');
2532:                                qr = new QueryResult(search);
2533:                                qr.setIssueId(pk);
2534:                                qr.setIdPrefix(searchRS.getString(2));
2535:                                qr.setIdCount(searchRS.getString(3));
2536:                                qr.setModuleId(new Integer(searchRS.getInt(4)));
2537:                                qr.setIssueTypeId(new Integer(searchRS
2538:                                        .getInt(5)));
2539:                                qr
2540:                                        .setCreatedBy(new Integer(searchRS
2541:                                                .getInt(6)));
2542:                                qr.setCreatedDate(searchRS.getTimestamp(7));
2543:                                qr
2544:                                        .setModifiedBy(new Integer(searchRS
2545:                                                .getInt(8)));
2546:                                qr.setModifiedDate(searchRS.getTimestamp(9));
2547:                                cachedQRs[count++] = qr;
2548:                            }
2549:                        }
2550:
2551:                        anyMoreResults = count > 0;
2552:                        if (anyMoreResults) {
2553:                            index = 0;
2554:                            pks.setLength(pks.length() - 1);
2555:
2556:                            // execute column result queries
2557:                            if (searchStuff.size() > 1) {
2558:                                Iterator i = searchStuff.iterator();
2559:                                i.next();
2560:                                while (i.hasNext()) {
2561:                                    ColumnBundle cb = (ColumnBundle) i.next();
2562:                                    StringBuffer sql = new StringBuffer(512);
2563:                                    sql.append(cb.select);
2564:
2565:                                    sql.append(FROM).append(
2566:                                            IssuePeer.TABLE_NAME);
2567:                                    if (cb.outerJoins != null) {
2568:                                        sql.append(cb.outerJoins);
2569:                                    }
2570:                                    sql.append(WHERE)
2571:                                            .append(IssuePeer.ISSUE_ID).append(
2572:                                                    IN).append(pks).append(')');
2573:                                    addOrderByClause(sql, cb.sortColumn);
2574:                                    Statement stmt = conn.createStatement();
2575:                                    ResultSet rs = stmt.executeQuery(sql
2576:                                            .toString());
2577:                                    rs.next();
2578:                                    stmtList.add(stmt);
2579:                                    rsList
2580:                                            .add(new ResultSetAndSize(rs,
2581:                                                    cb.size));
2582:                                }
2583:                            }
2584:                        }
2585:                    } else if (cachedQRs[index] == null) {
2586:                        anyMoreResults = false;
2587:                    }
2588:
2589:                    if (anyMoreResults) {
2590:                        // remove old results to allow gc, if needed
2591:                        if (index > 0) {
2592:                            cachedQRs[index - 1] = null;
2593:                        }
2594:                        nextQueryResult = cachedQRs[index++];
2595:                        buildQueryResult(nextQueryResult);
2596:                    }
2597:
2598:                    return anyMoreResults;
2599:                }
2600:
2601:                /**
2602:                 * Assembles one or more rows from a <code>ResultSet</code> into a
2603:                 * single {@link QueryResult} object.  Assumes that rows in the
2604:                 * <code>ResultSet</code> are grouped by issue.
2605:                 *
2606:                 * @return A single {@link QueryResult} object.
2607:                 * @exception SQLException If a database error occurs.
2608:                 */
2609:                private void buildQueryResult(QueryResult qr)
2610:                        throws SQLException {
2611:                    String queryResultPK = qr.getIssueId();
2612:                    Logger scarabLog = Log.get("org.tigris.scarab");
2613:
2614:                    // add column values
2615:                    int index = 0;
2616:                    for (Iterator iter = rsList.iterator(); iter.hasNext();) {
2617:                        ResultSetAndSize rss = (ResultSetAndSize) iter.next();
2618:                        ResultSet resultSet = rss.resultSet;
2619:                        int size = rss.size;
2620:                        String pk = resultSet.getString(1);
2621:                        while (queryResultPK.equals(pk)) {
2622:                            List values = qr.getAttributeValues();
2623:                            // Each attribute can result in a separate record.  As we
2624:                            // have sorted on the primary key column in addition to
2625:                            // any other sort, all attributes for a given issue will
2626:                            // be grouped.  Map these multiple records into a single
2627:                            // QueryResult per issue.
2628:                            if (values == null || index >= values.size()) {
2629:                                queryResultStarted(resultSet, qr, size);
2630:                                if (scarabLog.isDebugEnabled()) {
2631:                                    scarabLog
2632:                                            .debug("Fetching query result at index "
2633:                                                    + index
2634:                                                    + " with ID of "
2635:                                                    + queryResultPK);
2636:                                }
2637:                            } else {
2638:                                queryResultContinued(resultSet, qr, index, size);
2639:                            }
2640:
2641:                            pk = (resultSet.next()) ? resultSet.getString(1)
2642:                                    : null;
2643:                        }
2644:                        index += size;
2645:                    }
2646:                    qr.populateInternalAttributes(issueListAttributeColumns,
2647:                            L10N);
2648:                }
2649:
2650:                private void queryResultStarted(ResultSet rs, QueryResult qr,
2651:                        int valueListSize) throws SQLException {
2652:                    if (valueListSize > 0) {
2653:                        // Some attributes can be multivalued.
2654:                        List values = new ArrayList(valueListSize);
2655:                        for (int j = 0; j < valueListSize; j++) {
2656:                            ArrayList multiVal = new ArrayList(2);
2657:                            multiVal.add(rs.getString(j + 4));
2658:                            values.add(multiVal);
2659:                        }
2660:                        List lastValues = qr.getAttributeValues();
2661:                        if (lastValues == null) {
2662:                            qr.setAttributeValues(values);
2663:                        } else {
2664:                            lastValues.addAll(values);
2665:                        }
2666:                    }
2667:                }
2668:
2669:                private void queryResultContinued(ResultSet rs, QueryResult qr,
2670:                        int base, int valueListSize) throws SQLException {
2671:                    if (valueListSize > 0) {
2672:                        List values = qr.getAttributeValues();
2673:                        for (int j = 0; j < valueListSize; j++) {
2674:                            String s = rs.getString(j + 4);
2675:
2676:                            // As it's possible that multiple rows
2677:                            // could have the same value for a given
2678:                            // attribute, and we don't want to add the
2679:                            // same value many times, check for this
2680:                            // below.  See the code in the "else if"
2681:                            // block about 10 lines down to see how
2682:                            // the values lists are arranged to allow
2683:                            // for multiple values.
2684:                            List prevValues = (List) values.get(j + base);
2685:                            boolean newValue = true;
2686:                            for (int k = 0; k < prevValues.size(); k++) {
2687:                                if (ObjectUtils.equals(prevValues.get(k), s)) {
2688:                                    newValue = false;
2689:                                    break;
2690:                                }
2691:                            }
2692:                            if (newValue) {
2693:                                prevValues.add(s);
2694:                            }
2695:                        }
2696:                    }
2697:                }
2698:            }
2699:
2700:            private static class ColumnBundle {
2701:                int size;
2702:                StringBuffer select;
2703:                StringBuffer outerJoins;
2704:                String sortColumn;
2705:            }
2706:
2707:            private static class ResultSetAndSize {
2708:                private ResultSet resultSet;
2709:                private int size;
2710:
2711:                ResultSetAndSize(ResultSet rs, int s) {
2712:                    resultSet = rs;
2713:                    size = s;
2714:                }
2715:            }
2716:
2717:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.