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: }
|