View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.impl.document.search;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.joda.time.DateTime;
21  import org.kuali.rice.core.api.CoreApiServiceLocator;
22  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
23  import org.kuali.rice.core.api.uif.RemotableAttributeError;
24  import org.kuali.rice.core.api.uif.RemotableAttributeField;
25  import org.kuali.rice.core.api.util.RiceConstants;
26  import org.kuali.rice.core.framework.persistence.jdbc.sql.Criteria;
27  import org.kuali.rice.core.framework.persistence.jdbc.sql.SqlBuilder;
28  import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
29  import org.kuali.rice.kew.api.KewApiConstants;
30  import org.kuali.rice.kew.api.KewApiServiceLocator;
31  import org.kuali.rice.kew.api.document.Document;
32  import org.kuali.rice.kew.api.document.DocumentStatus;
33  import org.kuali.rice.kew.api.document.DocumentStatusCategory;
34  import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
35  import org.kuali.rice.kew.api.document.attribute.DocumentAttributeFactory;
36  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
37  import org.kuali.rice.kew.api.document.search.DocumentSearchResult;
38  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
39  import org.kuali.rice.kew.api.document.search.RouteNodeLookupLogic;
40  import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
41  import org.kuali.rice.kew.docsearch.QueryComponent;
42  import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
43  import org.kuali.rice.kew.doctype.bo.DocumentType;
44  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
45  import org.kuali.rice.kew.engine.node.RouteNode;
46  import org.kuali.rice.kew.service.KEWServiceLocator;
47  import org.kuali.rice.kew.util.PerformanceLogger;
48  import org.kuali.rice.kim.api.identity.Person;
49  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
50  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
51  import org.kuali.rice.krad.util.MessageMap;
52  
53  import java.sql.ResultSet;
54  import java.sql.SQLException;
55  import java.sql.Statement;
56  import java.sql.Timestamp;
57  import java.util.ArrayList;
58  import java.util.Collection;
59  import java.util.Collections;
60  import java.util.HashMap;
61  import java.util.HashSet;
62  import java.util.List;
63  import java.util.Map;
64  import java.util.Set;
65  import java.util.TreeSet;
66  
67  
68  /**
69   * Reference implementation of the {@code DocumentSearchGenerator}.
70   *
71   * @author Kuali Rice Team (rice.collab@kuali.org)
72   */
73  public class DocumentSearchGeneratorImpl implements DocumentSearchGenerator {
74  
75      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentSearchGeneratorImpl.class);
76  
77      private static final String ROUTE_NODE_TABLE = "KREW_RTE_NODE_T";
78      private static final String ROUTE_NODE_INST_TABLE = "KREW_RTE_NODE_INSTN_T";
79      private static final String DATABASE_WILDCARD_CHARACTER_STRING = "%";
80      private static final char DATABASE_WILDCARD_CHARACTER = DATABASE_WILDCARD_CHARACTER_STRING.toCharArray()[0];
81  
82      private DatabasePlatform dbPlatform;
83      private MessageMap messageMap;
84  
85      private SqlBuilder sqlBuilder = null;
86  
87      @Override
88      public DocumentSearchCriteria clearSearch(DocumentSearchCriteria criteria) {
89          return DocumentSearchCriteria.Builder.create().build();
90      }
91  
92      public DocumentType getValidDocumentType(String documentTypeFullName) {
93          if (!org.apache.commons.lang.StringUtils.isEmpty(documentTypeFullName)) {
94              DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByNameCaseInsensitive(documentTypeFullName);
95              if (documentType == null) {
96                  throw new RuntimeException("No Valid Document Type Found for document type name '" + documentTypeFullName + "'");
97              }
98              return documentType;
99          }
100         return null;
101     }
102 
103     @Override
104     public List<RemotableAttributeError> validateSearchableAttributes(DocumentSearchCriteria.Builder criteria) {
105         List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
106         DocumentType documentType = null;
107         try{
108               documentType = getValidDocumentType(criteria.getDocumentTypeName());
109         }catch(RuntimeException re){
110             errors.add(RemotableAttributeError.Builder.create("documentTypeName", re.getMessage()).build());
111         }
112 
113         if (documentType != null) {
114             errors = KEWServiceLocator.getDocumentSearchCustomizationMediator().validateLookupFieldParameters(documentType, criteria.build());
115         } else {
116             criteria.setDocumentAttributeValues(new HashMap<String, List<String>>());
117         }
118         return errors == null ? Collections.<RemotableAttributeError>emptyList() : Collections.unmodifiableList(errors);
119     }
120 
121     public QueryComponent getSearchableAttributeSql(Map<String, List<String>> documentAttributeValues, List<RemotableAttributeField> searchFields, String whereClausePredicatePrefix) {
122 
123         StringBuilder fromSql = new StringBuilder();
124         StringBuilder whereSql = new StringBuilder();
125 
126         //Map<String, List<SearchAttributeCriteriaComponent>> searchableAttributeRangeComponents = new HashMap<String,List<SearchAttributeCriteriaComponent>>();
127         Criteria finalCriteria = null;
128         int tableIndex = 1;
129         SqlBuilder sqlBuilder = this.getSqlBuilder();
130 
131         for (String documentAttributeName : documentAttributeValues.keySet()) {
132             String documentAttributeNameForSQL = documentAttributeName;
133             if (documentAttributeName.contains(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX)) {
134                 documentAttributeNameForSQL = documentAttributeName.replaceFirst(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX, "");
135             }
136             List<String> searchValues = documentAttributeValues.get(documentAttributeName);
137             if (CollectionUtils.isEmpty(searchValues)) {
138                 continue;
139             }
140 
141             String tableAlias = "EXT" + tableIndex;
142             RemotableAttributeField searchField = getSearchFieldByName(documentAttributeName, searchFields);
143             String tableName = DocumentSearchInternalUtils.getAttributeTableName(searchField);
144             boolean caseSensitive = DocumentSearchInternalUtils.isLookupCaseSensitive(searchField);
145 
146             Criteria crit = null;
147 
148             Class<?> dataTypeClass = DocumentSearchInternalUtils.getDataTypeClass(searchField);
149             if (searchValues.size() > 1) {
150                 // if there's more than one entry, we need to do an "in"
151                 crit = new Criteria(tableName, tableAlias);
152                 crit.setDbPlatform(sqlBuilder.getDbPlatform());
153                 crit.in("VAL", searchValues, dataTypeClass);
154             } else {
155                 crit = sqlBuilder.createCriteria("VAL", searchValues.get(0) , tableName, tableAlias, dataTypeClass, !caseSensitive);
156             }
157 
158             sqlBuilder.addCriteria("KEY_CD", documentAttributeNameForSQL, String.class, false, false, crit); // this is always of type string.
159             sqlBuilder.andCriteria("DOC_HDR_ID", tableAlias + ".DOC_HDR_ID", "KREW_DOC_HDR_T", "DOC_HDR", SqlBuilder.JoinType.class, false, false, crit);
160 
161             if (finalCriteria == null ){
162                 finalCriteria = crit;
163             } else{
164                 sqlBuilder.andCriteria(finalCriteria, crit);
165             }
166 
167             // - below is the old code
168             // if where clause is empty then use passed in prefix... otherwise generate one
169             String whereClausePrefix = (whereSql.length() == 0) ? whereClausePredicatePrefix : getGeneratedPredicatePrefix(whereSql.length());
170             QueryComponent qc = generateSearchableAttributeSql(tableName, documentAttributeNameForSQL, whereClausePrefix, tableIndex);
171             fromSql.append(qc.getFromSql());
172             tableIndex++;
173         }
174 
175         if (finalCriteria == null) {
176             return new QueryComponent("", "", "");
177         }
178 
179         String whereClausePrefix = (whereSql.length() == 0) ? whereClausePredicatePrefix : getGeneratedPredicatePrefix(whereSql.length());
180 
181         return new QueryComponent("", fromSql.toString(), whereClausePrefix + " " + finalCriteria.buildWhere());
182     }
183 
184     private RemotableAttributeField getSearchFieldByName(String fieldName, List<RemotableAttributeField> searchFields) {
185         for (RemotableAttributeField searchField : searchFields) {
186             if (searchField.getName().equals(fieldName)
187                     || searchField.getName().equals(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + fieldName)) {
188                 return searchField;
189             }
190         }
191         throw new IllegalStateException("Failed to locate a RemotableAttributeField for fieldName=" + fieldName);
192     }
193 
194     public QueryComponent generateSearchableAttributeSql(String tableName, String documentAttributeName, String whereSqlStarter,int tableIndex) {
195         String tableIdentifier = "EXT" + tableIndex;
196         QueryComponent joinSqlComponent = getSearchableAttributeJoinSql(tableName, tableIdentifier, whereSqlStarter, documentAttributeName);
197         return new QueryComponent("", joinSqlComponent.getFromSql(), joinSqlComponent.getWhereSql());
198     }
199 
200     public QueryComponent getSearchableAttributeJoinSql(String tableName, String tableIdentifier, String whereSqlStarter, String attributeTableKeyColumnName) {
201         return new QueryComponent("", generateSearchableAttributeFromSql(tableName, tableIdentifier).toString(), generateSearchableAttributeWhereClauseJoin(whereSqlStarter, tableIdentifier, attributeTableKeyColumnName).toString());
202     }
203 
204     public StringBuilder generateSearchableAttributeWhereClauseJoin(String whereSqlStarter,String tableIdentifier,String attributeTableKeyColumnName) {
205         StringBuilder whereSql = new StringBuilder(constructWhereClauseElement(whereSqlStarter, "DOC_HDR.DOC_HDR_ID", "=", getDbPlatform().escapeString(tableIdentifier + ".DOC_HDR_ID"), null, null));
206         whereSql.append(constructWhereClauseElement(" and ", tableIdentifier + ".KEY_CD", "=",
207                 getDbPlatform().escapeString(attributeTableKeyColumnName), "'", "'"));
208         return whereSql;
209     }
210 
211     public StringBuilder generateSearchableAttributeFromSql(String tableName, String tableIdentifier) {
212         if (StringUtils.isBlank(tableName)) {
213             throw new IllegalArgumentException("tableName was null or blank");
214         }
215         if (StringUtils.isBlank(tableIdentifier)) {
216             throw new IllegalArgumentException("tableIdentifier was null or blank");
217         }
218         StringBuilder fromSql = new StringBuilder();
219         fromSql.append(" ,").append(tableName).append(" ").append(getDbPlatform().escapeString(tableIdentifier)).append(" ");
220         return fromSql;
221     }
222 
223     public StringBuilder constructWhereClauseElement(String clauseStarter,String queryTableColumnName,String operand,String valueToSearch,String valuePrefix,String valueSuffix) {
224         StringBuilder whereSql = new StringBuilder();
225         valuePrefix = (valuePrefix != null) ? valuePrefix : "";
226         valueSuffix = (valueSuffix != null) ? valueSuffix : "";
227         whereSql.append(" " + clauseStarter + " ").append(getDbPlatform().escapeString(queryTableColumnName)).append(" " + operand + " ").append(valuePrefix).append(valueToSearch).append(valueSuffix).append(" ");
228         return whereSql;
229     }
230 
231     @Override
232     public DocumentSearchResults.Builder processResultSet(DocumentSearchCriteria criteria, boolean criteriaModified, Statement searchAttributeStatement, ResultSet resultSet, int maxResultCap, int fetchLimit) throws SQLException {
233         DocumentSearchCriteria.Builder criteriaBuilder = DocumentSearchCriteria.Builder.create(criteria);
234         DocumentSearchResults.Builder results = DocumentSearchResults.Builder.create(criteriaBuilder);
235         results.setCriteriaModified(criteriaModified);
236         int size = 0;
237         List<DocumentSearchResult.Builder> resultList = new ArrayList<DocumentSearchResult.Builder>();
238         results.setSearchResults(resultList);
239         Map<String, DocumentSearchResult.Builder> resultMap = new HashMap<String, DocumentSearchResult.Builder>();
240         PerformanceLogger perfLog = new PerformanceLogger();
241         int iteration = 0;
242         int startAt = (criteria.getStartAtIndex()==null) ? 0 : criteria.getStartAtIndex();
243         maxResultCap += startAt;
244         boolean resultSetHasNext = resultSet.next();
245         while ( resultSetHasNext && resultMap.size() < maxResultCap && iteration++ < fetchLimit && startAt >= 0) {
246 
247             if(iteration <= startAt) {
248                 resultSetHasNext = resultSet.next();
249                 continue;
250             }
251 
252             DocumentSearchResult.Builder resultBuilder = processRow(criteria, searchAttributeStatement, resultSet);
253             String documentId = resultBuilder.getDocument().getDocumentId();
254             if (!resultMap.containsKey(documentId)) {
255                 resultList.add(resultBuilder);
256                 resultMap.put(documentId, resultBuilder);
257                 size++;
258             } else {
259                 // handle duplicate rows with different search data
260                 DocumentSearchResult.Builder previousEntry = resultMap.get(documentId);
261                 handleMultipleDocumentRows(previousEntry, resultBuilder);
262             }
263             resultSetHasNext = resultSet.next();
264         }
265 
266         perfLog.log("Time to read doc search results.", true);
267         // if we have threshold+1 results, then we have more results than we are going to display
268         results.setOverThreshold(resultSetHasNext);
269 
270         LOG.debug("Processed "+size+" document search result rows.");
271         return results;
272     }
273 
274     /**
275      * Handles multiple document rows by collapsing them into the list of document attributes on the existing row.
276      * The two rows must represent the same document.
277      *
278      * @param existingRow the existing row to combine the new row into
279      * @param newRow the new row from which to combine document attributes with the existing row
280      */
281     private void handleMultipleDocumentRows(DocumentSearchResult.Builder existingRow, DocumentSearchResult.Builder newRow) {
282         for (DocumentAttribute.AbstractBuilder<?> newDocumentAttribute : newRow.getDocumentAttributes()) {
283             existingRow.getDocumentAttributes().add(newDocumentAttribute);
284         }
285     }
286 
287     /**
288      * Processes the search result row, returning a DocumentSearchResult
289      * @param criteria the original search criteria
290      * @param searchAttributeStatement statement being used to call the database for queries
291      * @param rs the search result set
292      * @return a DocumentSearchResult representing the current ResultSet row
293      * @throws SQLException
294      */
295     protected DocumentSearchResult.Builder processRow(DocumentSearchCriteria criteria, Statement searchAttributeStatement, ResultSet rs) throws SQLException {
296 
297         String documentId = rs.getString("DOC_HDR_ID");
298         String initiatorPrincipalId = rs.getString("INITR_PRNCPL_ID");
299         String documentTypeName = rs.getString("DOC_TYP_NM");
300         org.kuali.rice.kew.api.doctype.DocumentType documentType =
301                 KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(documentTypeName);
302         if (documentType == null) {
303             throw new IllegalStateException("Failed to locate a document type with the given name: " + documentTypeName);
304         }
305         String documentTypeId = documentType.getId();
306 
307         Document.Builder documentBuilder = Document.Builder.create(documentId, initiatorPrincipalId, documentTypeName, documentTypeId);
308         DocumentSearchResult.Builder resultBuilder = DocumentSearchResult.Builder.create(documentBuilder);
309 
310         String statusCode = rs.getString("DOC_HDR_STAT_CD");
311         Timestamp createTimestamp = rs.getTimestamp("CRTE_DT");
312         String title = rs.getString("TTL");
313         String applicationDocumentStatus = rs.getString("APP_DOC_STAT");
314 
315         documentBuilder.setStatus(DocumentStatus.fromCode(statusCode));
316         documentBuilder.setDateCreated(new DateTime(createTimestamp.getTime()));
317         documentBuilder.setTitle(title);
318         documentBuilder.setApplicationDocumentStatus(applicationDocumentStatus);
319         documentBuilder.setApplicationDocumentStatusDate(new DateTime(rs.getTimestamp("APP_DOC_STAT_MDFN_DT")));
320         documentBuilder.setDateApproved(new DateTime(rs.getTimestamp("APRV_DT")));
321         documentBuilder.setDateFinalized(new DateTime(rs.getTimestamp("FNL_DT")));
322         documentBuilder.setApplicationDocumentId(rs.getString("APP_DOC_ID"));
323         documentBuilder.setDateLastModified(new DateTime(rs.getTimestamp("STAT_MDFN_DT")));
324         documentBuilder.setRoutedByPrincipalId(rs.getString("RTE_PRNCPL_ID"));
325 
326         // TODO - KULRICE-5755 - should probably set as many properties on the document as we can
327         documentBuilder.setDocumentHandlerUrl(rs.getString("DOC_HDLR_URL"));
328 
329         if (isUsingAtLeastOneSearchAttribute(criteria)) {
330             populateDocumentAttributesValues(resultBuilder, searchAttributeStatement);
331         }
332 
333         return resultBuilder;
334     }
335 
336     /**
337      * This method performs searches against the search attribute value tables (see classes implementing
338      * {@link org.kuali.rice.kew.docsearch.SearchableAttributeValue}) to get data to fill in search attribute values on the given resultBuilder parameter
339      *
340      * @param resultBuilder - document search result object getting search attributes added to it
341      * @param searchAttributeStatement - statement being used to call the database for queries
342      * @throws SQLException
343      */
344     public void populateDocumentAttributesValues(DocumentSearchResult.Builder resultBuilder, Statement searchAttributeStatement) throws SQLException {
345         searchAttributeStatement.setFetchSize(50);
346         String documentId = resultBuilder.getDocument().getDocumentId();
347         List<SearchableAttributeValue> attributeValues = DocumentSearchInternalUtils
348                 .getSearchableAttributeValueObjectTypes();
349         PerformanceLogger perfLog = new PerformanceLogger(documentId);
350         for (SearchableAttributeValue searchAttValue : attributeValues) {
351             String attributeSql = "select KEY_CD, VAL from " + searchAttValue.getAttributeTableName() + " where DOC_HDR_ID = '" + documentId + "'";
352             ResultSet attributeResultSet = null;
353             try {
354                 attributeResultSet = searchAttributeStatement.executeQuery(attributeSql);
355                 while (attributeResultSet.next()) {
356                     searchAttValue.setSearchableAttributeKey(attributeResultSet.getString("KEY_CD"));
357                     searchAttValue.setupAttributeValue(attributeResultSet, "VAL");
358                     if ( (!org.apache.commons.lang.StringUtils.isEmpty(searchAttValue.getSearchableAttributeKey())) && (searchAttValue.getSearchableAttributeValue() != null) ) {
359                         DocumentAttribute documentAttribute = searchAttValue.toDocumentAttribute();
360                         resultBuilder.getDocumentAttributes().add(DocumentAttributeFactory.loadContractIntoBuilder(
361                                 documentAttribute));
362                     }
363                 }
364             } finally {
365                 if (attributeResultSet != null) {
366                     try {
367                         attributeResultSet.close();
368                     } catch (Exception e) {
369                         LOG.warn("Could not close searchable attribute result set for class " + searchAttValue.getClass().getName(),e);
370                     }
371                 }
372             }
373         }
374         perfLog.log("Time to execute doc search search attribute queries.", true);
375     }
376 
377     @SuppressWarnings("deprecation")
378     public String generateSearchSql(DocumentSearchCriteria criteria, List<RemotableAttributeField> searchFields) {
379 
380         String docTypeTableAlias   = "DOC1";
381         String docHeaderTableAlias = "DOC_HDR";
382 
383         String sqlPrefix = "Select * from (";
384         String sqlSuffix = ") FINAL_SEARCH order by FINAL_SEARCH.CRTE_DT desc";
385         
386         // the DISTINCT here is important as it filters out duplicate rows which could occur as the result of doc search extension values...
387         StringBuilder selectSQL = new StringBuilder("select DISTINCT("+ docHeaderTableAlias +".DOC_HDR_ID), "
388                                                     + StringUtils.join(new String[] {
389                                                         docHeaderTableAlias + ".INITR_PRNCPL_ID",
390                                                         docHeaderTableAlias + ".DOC_HDR_STAT_CD",
391                                                         docHeaderTableAlias + ".CRTE_DT",
392                                                         docHeaderTableAlias + ".TTL",
393                                                         docHeaderTableAlias + ".APP_DOC_STAT",
394                                                         docHeaderTableAlias + ".STAT_MDFN_DT",
395                                                         docHeaderTableAlias + ".APRV_DT",
396                                                         docHeaderTableAlias + ".FNL_DT",
397                                                         docHeaderTableAlias + ".APP_DOC_ID",
398                                                         docHeaderTableAlias + ".RTE_PRNCPL_ID",
399                                                         docHeaderTableAlias + ".APP_DOC_STAT_MDFN_DT",
400                                                         docTypeTableAlias + ".DOC_TYP_NM",
401                                                         docTypeTableAlias + ".LBL",
402                                                         docTypeTableAlias + ".DOC_HDLR_URL",
403                                                         docTypeTableAlias + ".ACTV_IND"
404                                                     }, ", "));
405         StringBuilder fromSQL = new StringBuilder(" from KREW_DOC_TYP_T "+ docTypeTableAlias +" ");
406         StringBuilder fromSQLForDocHeaderTable = new StringBuilder(", KREW_DOC_HDR_T " + docHeaderTableAlias + " ");
407 
408         StringBuilder whereSQL = new StringBuilder();
409         whereSQL.append(getDocumentIdSql(criteria.getDocumentId(), getGeneratedPredicatePrefix(whereSQL.length()), docHeaderTableAlias));
410         // if principalId criteria exists ignore deprecated principalName search term
411         String principalInitiatorIdSql = getInitiatorIdSql(criteria.getInitiatorPrincipalId(), getGeneratedPredicatePrefix(whereSQL.length()));
412         if (StringUtils.isNotBlank(principalInitiatorIdSql)) {
413             whereSQL.append(principalInitiatorIdSql);
414         } else {
415             whereSQL.append(getInitiatorSql(criteria.getInitiatorPrincipalName(), getGeneratedPredicatePrefix(whereSQL.length())));
416         }
417         whereSQL.append(getAppDocIdSql(criteria.getApplicationDocumentId(), getGeneratedPredicatePrefix(whereSQL.length())));
418         whereSQL.append(getDateCreatedSql(criteria.getDateCreatedFrom(), criteria.getDateCreatedTo(), getGeneratedPredicatePrefix(whereSQL.length())));
419         whereSQL.append(getDateLastModifiedSql(criteria.getDateLastModifiedFrom(), criteria.getDateLastModifiedTo(), getGeneratedPredicatePrefix(whereSQL.length())));
420         whereSQL.append(getDateApprovedSql(criteria.getDateApprovedFrom(), criteria.getDateApprovedTo(), getGeneratedPredicatePrefix(whereSQL.length())));
421         whereSQL.append(getDateFinalizedSql(criteria.getDateFinalizedFrom(), criteria.getDateFinalizedTo(), getGeneratedPredicatePrefix(whereSQL.length())));
422 
423         // flags for the table being added to the FROM class of the sql
424         String principalViewerSql = getViewerSql(criteria.getViewerPrincipalName(), getGeneratedPredicatePrefix(whereSQL.length()));
425         String principalViewerIdSql = getViewerIdSql(criteria.getViewerPrincipalId(), getGeneratedPredicatePrefix(whereSQL.length()));
426         // if principalId criteria exists ignore deprecated principalName search term
427         if (StringUtils.isNotBlank(principalViewerIdSql)){
428             principalViewerSql = "";
429         }
430         String groupViewerSql = getGroupViewerSql(criteria.getGroupViewerId(), getGeneratedPredicatePrefix(whereSQL.length()));
431         if (StringUtils.isNotBlank(principalViewerSql) || StringUtils.isNotBlank(groupViewerSql) || StringUtils.isNotBlank(principalViewerIdSql) ) {
432             whereSQL.append(principalViewerSql);
433             whereSQL.append(principalViewerIdSql);
434             whereSQL.append(groupViewerSql);
435             fromSQL.append(", KREW_ACTN_RQST_T ");
436         }
437 
438         String principalApproverSql =  getApproverSql(criteria.getApproverPrincipalName(), getGeneratedPredicatePrefix(whereSQL.length()));
439         String principalApproverIdSql = getApproverIdSql(criteria.getApproverPrincipalId(), getGeneratedPredicatePrefix(whereSQL.length()));
440         // if principalId criteria exists ignore deprecated principalName search term
441         if (StringUtils.isNotBlank(principalApproverIdSql)){
442             principalApproverSql = "";
443         }
444         if (StringUtils.isNotBlank(principalApproverSql) || StringUtils.isNotBlank(principalApproverIdSql)) {
445             whereSQL.append(principalApproverSql);
446             whereSQL.append(principalApproverIdSql);
447             fromSQL.append(", KREW_ACTN_TKN_T ");
448         }
449 
450 
451 
452         String docRouteNodeSql = getDocRouteNodeSql(criteria.getDocumentTypeName(), criteria.getRouteNodeName(), criteria.getRouteNodeLookupLogic(), getGeneratedPredicatePrefix(whereSQL.length()));
453         if (StringUtils.isNotBlank(docRouteNodeSql)) {
454             whereSQL.append(docRouteNodeSql);
455             fromSQL.append(", KREW_RTE_NODE_INSTN_T ");
456             fromSQL.append(", KREW_RTE_NODE_T ");
457         }
458 
459         if (!criteria.getDocumentAttributeValues().isEmpty()) {
460             QueryComponent queryComponent = getSearchableAttributeSql(criteria.getDocumentAttributeValues(), searchFields, getGeneratedPredicatePrefix(
461                     whereSQL.length()));
462             selectSQL.append(queryComponent.getSelectSql());
463             fromSQL.append(queryComponent.getFromSql());
464             whereSQL.append(queryComponent.getWhereSql());
465         }
466 
467         whereSQL.append(getDocTypeFullNameWhereSql(criteria, getGeneratedPredicatePrefix(whereSQL.length())));
468         whereSQL.append(getDocTitleSql(criteria.getTitle(), getGeneratedPredicatePrefix(whereSQL.length())));
469         whereSQL.append(getDocumentStatusSql(criteria.getDocumentStatuses(), criteria.getDocumentStatusCategories(), getGeneratedPredicatePrefix(whereSQL.length())));
470         whereSQL.append(getGeneratedPredicatePrefix(whereSQL.length())).append(" DOC_HDR.DOC_TYP_ID = DOC1.DOC_TYP_ID ");
471         fromSQL.append(fromSQLForDocHeaderTable);
472 
473         // App Doc Status Value and Transition clauses
474         String statusTransitionWhereClause = getStatusTransitionDateSql(criteria.getDateApplicationDocumentStatusChangedFrom(), criteria.getDateApplicationDocumentStatusChangedTo(), getGeneratedPredicatePrefix(whereSQL.length()));
475 
476         List<String> applicationDocumentStatuses = criteria.getApplicationDocumentStatuses();
477         // deal with legacy usage of applicationDocumentStatus (which is deprecated)
478         if (!StringUtils.isBlank(criteria.getApplicationDocumentStatus())) {
479             if (!criteria.getApplicationDocumentStatuses().contains(criteria.getApplicationDocumentStatus())) {
480                 applicationDocumentStatuses = new ArrayList<String>(criteria.getApplicationDocumentStatuses());
481                 applicationDocumentStatuses.add(criteria.getApplicationDocumentStatus());
482             }
483         }
484 
485         whereSQL.append(getAppDocStatusesSql(applicationDocumentStatuses, getGeneratedPredicatePrefix(
486                 whereSQL.length()), statusTransitionWhereClause.length()));
487         if (statusTransitionWhereClause.length() > 0){
488         	whereSQL.append(statusTransitionWhereClause);
489             whereSQL.append(getGeneratedPredicatePrefix(whereSQL.length())).append(" DOC_HDR.DOC_HDR_ID = STAT_TRAN.DOC_HDR_ID ");
490         	fromSQL.append(", KREW_APP_DOC_STAT_TRAN_T STAT_TRAN ");
491         }
492 
493         String finalizedSql = sqlPrefix + " " + selectSQL.toString() + " " + fromSQL.toString() + " " + whereSQL.toString() + " " + sqlSuffix;
494 
495         LOG.info("*********** SEARCH SQL ***************");
496         LOG.info(finalizedSql);
497         LOG.info("**************************************");
498         return finalizedSql;
499     }
500 
501     public String getDocumentIdSql(String documentId, String whereClausePredicatePrefix, String tableAlias) {
502         if (StringUtils.isBlank(documentId)) {
503             return "";
504         } else {
505         	// Using true for caseInsensitive causes bad performance for MYSQL databases since function indexes cannot be added.
506         	// Due to this, false is passed for caseInsensitive
507             Criteria crit = getSqlBuilder().createCriteria("DOC_HDR_ID", documentId, "KREW_DOC_HDR_T", tableAlias, String.class, false, true);
508             return new StringBuilder(whereClausePredicatePrefix + crit.buildWhere()).toString();
509         }
510     }
511 
512     public String getDocTitleSql(String docTitle, String whereClausePredicatePrefix) {
513         if (StringUtils.isBlank(docTitle)) {
514             return "";
515         } else {
516             // quick and dirty ' replacement that isn't the best but should work for all dbs
517             docTitle = docTitle.trim().replace("\'", "\'\'");
518             SqlBuilder sqlBuild = new SqlBuilder();
519             Criteria crit = new Criteria("KREW_DOC_HDR_T", "DOC_HDR");
520             sqlBuild.addCriteria("TTL", docTitle, String.class, true, true, crit);
521             return new StringBuilder(whereClausePredicatePrefix + crit.buildWhere()).toString();
522         }
523     }
524 
525     // special methods that return the sql needed to complete the search
526     // or nothing if the field was not filled in
527     public String getAppDocIdSql(String appDocId, String whereClausePredicatePrefix) {
528         if (StringUtils.isBlank(appDocId)) {
529             return "";
530         } else {
531             String tableAlias = "DOC_HDR";
532             Criteria crit = getSqlBuilder().createCriteria("APP_DOC_ID", appDocId, "KREW_DOC_HDR_T", tableAlias,String.class);
533             return new StringBuilder(whereClausePredicatePrefix + crit.buildWhere()).toString();
534         }
535     }
536 
537     public String getDateCreatedSql(DateTime fromDateCreated, DateTime toDateCreated, String whereClausePredicatePrefix) {
538         return establishDateString(fromDateCreated, toDateCreated, "KREW_DOC_HDR_T", "DOC_HDR", "CRTE_DT", whereClausePredicatePrefix);
539     }
540 
541     public String getDateApprovedSql(DateTime fromDateApproved, DateTime toDateApproved, String whereClausePredicatePrefix) {
542         return establishDateString(fromDateApproved, toDateApproved, "KREW_DOC_HDR_T", "DOC_HDR", "APRV_DT", whereClausePredicatePrefix);
543     }
544 
545     public String getDateFinalizedSql(DateTime fromDateFinalized, DateTime toDateFinalized, String whereClausePredicatePrefix) {
546         return establishDateString(fromDateFinalized, toDateFinalized, "KREW_DOC_HDR_T", "DOC_HDR", "FNL_DT", whereClausePredicatePrefix);
547     }
548 
549     public String getDateLastModifiedSql(DateTime fromDateLastModified, DateTime toDateLastModified, String whereClausePredicatePrefix) {
550         return establishDateString(fromDateLastModified, toDateLastModified, "KREW_DOC_HDR_T", "DOC_HDR", "STAT_MDFN_DT", whereClausePredicatePrefix);
551     }
552 
553 	public String getStatusTransitionDateSql(DateTime fromStatusTransitionDate, DateTime toStatusTransitionDate, String whereClausePredicatePrefix) {
554         return establishDateString(fromStatusTransitionDate, toStatusTransitionDate, "KREW_DOC_HDR_T", "DOC_HDR", "APP_DOC_STAT_MDFN_DT", whereClausePredicatePrefix);
555     }
556 
557     public String getViewerSql(String viewer, String whereClausePredicatePrefix) {
558         StringBuilder returnSql = new StringBuilder();
559         if (StringUtils.isNotBlank(viewer)) {
560             Map<String, String> m = new HashMap<String, String>();
561             m.put("principalName", viewer);
562 
563             // This will search for people with the ability for the valid operands.
564             List<Person> personList = KimApiServiceLocator.getPersonService().findPeople(m, false);
565             List<String> principalList = new ArrayList<String>();
566 
567             if(CollectionUtils.isEmpty(personList)) {
568             	// findPeople allows for wildcards, but the person must be active.  If no one was found,
569             	// check for an exact inactive user.
570                 PrincipalContract tempPrincipal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(viewer.trim());
571         		if (tempPrincipal != null) {
572                     principalList.add(tempPrincipal.getPrincipalId());
573             	} else {
574                     // they entered something that returned nothing... so we should return nothing
575 
576                     return new StringBuilder(whereClausePredicatePrefix + " 1 = 0 ").toString();
577             	}
578             }
579 
580             for (Person person : personList){
581                 principalList.add(person.getPrincipalId());
582             }
583 
584             Criteria crit = new Criteria("KREW_ACTN_RQST_T", "KREW_ACTN_RQST_T");
585             crit.in("PRNCPL_ID", principalList, String.class);
586             returnSql.append(whereClausePredicatePrefix + "( (DOC_HDR.DOC_HDR_ID = KREW_ACTN_RQST_T.DOC_HDR_ID and " + crit.buildWhere() + " )");
587 
588             Set<String> viewerGroupIds = new TreeSet<String>();
589 
590             if(CollectionUtils.isNotEmpty(principalList)) {
591                 for(String principalId: principalList){
592                     viewerGroupIds.addAll(KimApiServiceLocator.getGroupService().getGroupIdsByPrincipalId(principalId));
593                 }
594             }
595 
596             // Documents routed to users as part of a workgoup should be returned.
597             // Use Chad's escape stuff
598             if (viewerGroupIds != null && !viewerGroupIds.isEmpty()) {
599 
600                 returnSql.append(" or ( " +
601                     "DOC_HDR.DOC_HDR_ID = KREW_ACTN_RQST_T.DOC_HDR_ID " +
602                     "and KREW_ACTN_RQST_T.GRP_ID in (");
603 
604                 boolean first = true;
605                 for (String groupId : viewerGroupIds){
606                     if(!first){
607                         returnSql.append(",");
608                     }
609                     returnSql.append("'").append(groupId).append("'");
610                     first = false;
611                 }
612                 returnSql.append("))");
613             }
614             returnSql.append(")");
615         }
616         return returnSql.toString();
617     }
618 
619     public String getViewerIdSql(String viewerId, String whereClausePredicatePrefix) {
620         StringBuilder returnSql = new StringBuilder();
621         if (StringUtils.isNotBlank(viewerId)) {
622             Map<String, String> m = new HashMap<String, String>();
623             m.put("principalId", viewerId);
624 
625             // This will search for people with the ability for the valid operands.
626             List<Person> personList = KimApiServiceLocator.getPersonService().findPeople(m, false);
627             List<String> principalList = new ArrayList<String>();
628 
629             if(CollectionUtils.isEmpty(personList)) {
630                 // they entered something that returned nothing... so we should return nothing
631                 return new StringBuilder(whereClausePredicatePrefix + " 1 = 0 ").toString();
632             }
633 
634             for (Person person : personList){
635                 principalList.add(person.getPrincipalId());
636             }
637 
638             Criteria crit = new Criteria("KREW_ACTN_RQST_T", "KREW_ACTN_RQST_T");
639             crit.in("PRNCPL_ID", principalList, String.class);
640             returnSql.append(whereClausePredicatePrefix + "( DOC_HDR.DOC_HDR_ID = KREW_ACTN_RQST_T.DOC_HDR_ID and " + crit.buildWhere() + " )");
641         }
642         return returnSql.toString();
643     }
644 
645     public String getGroupViewerSql(String groupId, String whereClausePredicatePrefix) {
646         String sql = "";
647         if (StringUtils.isNotBlank(groupId)) {
648             sql = whereClausePredicatePrefix + " DOC_HDR.DOC_HDR_ID = KREW_ACTN_RQST_T.DOC_HDR_ID and KREW_ACTN_RQST_T.GRP_ID = '" + groupId + "'";
649         }
650         return sql;
651     }
652 
653     public String getInitiatorSql(String initiatorPrincipalName, String whereClausePredicatePrefix) {
654 
655         if (StringUtils.isBlank(initiatorPrincipalName)) {
656             return "";
657         }
658 
659         String tableAlias = "DOC_HDR";
660 
661         Map<String, String> m = new HashMap<String, String>();
662         m.put("principalName", initiatorPrincipalName);
663 
664         // This will search for people with the ability for the valid operands.
665         List<Person> pList = KimApiServiceLocator.getPersonService().findPeople(m, false);
666         List<String> principalList = new ArrayList<String>();
667 
668         if(pList == null || pList.isEmpty() ){
669        		// findPeople allows for wildcards, but the person must be active.  If no one was found,
670        		// check for an exact inactive user.
671        		PrincipalContract tempPrincipal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(initiatorPrincipalName.trim());
672        		if (tempPrincipal != null) {
673        			principalList.add(tempPrincipal.getPrincipalId());
674        		} else {
675                 // they entered something that returned nothing... so we should return nothing
676                 return new StringBuilder(whereClausePredicatePrefix + " 1 = 0 ").toString();
677         	}
678         }
679 
680         for(Person p: pList){
681             principalList.add(p.getPrincipalId());
682         }
683 
684         Criteria crit = new Criteria("KREW_DOC_HDR_T", tableAlias);
685         crit.in("INITR_PRNCPL_ID", principalList, String.class);
686 
687         return new StringBuilder(whereClausePredicatePrefix + crit.buildWhere()).toString();
688     }
689 
690     public String getInitiatorIdSql(String initiatorPrincipalId, String whereClausePredicatePrefix) {
691 
692         if (StringUtils.isBlank(initiatorPrincipalId)) {
693             return "";
694         }
695 
696         String tableAlias = "DOC_HDR";
697 
698         Map<String, String> m = new HashMap<String, String>();
699         m.put("principalId", initiatorPrincipalId);
700 
701         // This will search for people with the ability for the valid operands.
702         List<Person> pList = KimApiServiceLocator.getPersonService().findPeople(m, false);
703         List<String> principalList = new ArrayList<String>();
704 
705         if(pList == null || pList.isEmpty() ){
706             // they entered something that returned nothing... so we should return nothing
707             return new StringBuilder(whereClausePredicatePrefix + " 1 = 0 ").toString();
708         }
709 
710         for(Person p: pList){
711             principalList.add(p.getPrincipalId());
712         }
713 
714         Criteria crit = new Criteria("KREW_DOC_HDR_T", tableAlias);
715         crit.in("INITR_PRNCPL_ID", principalList, String.class);
716 
717         return new StringBuilder(whereClausePredicatePrefix + crit.buildWhere()).toString();
718     }
719 
720     public String getApproverSql(String approver, String whereClausePredicatePrefix) {
721         String returnSql = "";
722         if (StringUtils.isNotBlank(approver)) {
723             Map<String, String> m = new HashMap<String, String>();
724             m.put("principalName", approver);
725 
726             // This will search for people with the ability for the valid operands.
727             List<Person> pList = KimApiServiceLocator.getPersonService().findPeople(m, false);
728             List<String> principalList = new ArrayList<String>();
729 
730             if(pList == null || pList.isEmpty() ){
731            		// findPeople allows for wildcards, but the person must be active.  If no one was found,
732            		// check for an exact inactive user.
733                 PrincipalContract tempPrincipal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(approver.trim());
734 
735                 if (tempPrincipal != null) {
736            			principalList.add(tempPrincipal.getPrincipalId());
737                 } else {
738                     // they entered something that returned nothing... so we should return nothing
739                     return new StringBuilder(whereClausePredicatePrefix + " 1 = 0 ").toString();
740                 }
741             }
742 
743             for(Person p: pList){
744                 principalList.add(p.getPrincipalId());
745             }
746 
747             Criteria crit = new Criteria("KREW_ACTN_TKN_T", "KREW_ACTN_TKN_T");
748             crit.in("PRNCPL_ID", principalList, String.class);
749 
750             returnSql = whereClausePredicatePrefix +
751             " DOC_HDR.DOC_HDR_ID = KREW_ACTN_TKN_T.DOC_HDR_ID and upper(KREW_ACTN_TKN_T.ACTN_CD) in ('" +
752             KewApiConstants.ACTION_TAKEN_APPROVED_CD + "','" + KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD + "')" +
753             " and " + crit.buildWhere();
754         }
755         return returnSql;
756     }
757 
758     public String getApproverIdSql(String approverId, String whereClausePredicatePrefix) {
759         String returnSql = "";
760         if (StringUtils.isNotBlank(approverId)) {
761             Map<String, String> m = new HashMap<String, String>();
762             m.put("principalId", approverId);
763 
764             // This will search for people with the ability for the valid operands.
765             List<Person> pList = KimApiServiceLocator.getPersonService().findPeople(m, false);
766             List<String> principalList = new ArrayList<String>();
767 
768             if(pList == null || pList.isEmpty() ){
769                  // they entered something that returned nothing... so we should return nothing
770                     return new StringBuilder(whereClausePredicatePrefix + " 1 = 0 ").toString();
771             }
772 
773             for(Person p: pList){
774                 principalList.add(p.getPrincipalId());
775             }
776 
777             Criteria crit = new Criteria("KREW_ACTN_TKN_T", "KREW_ACTN_TKN_T");
778             crit.in("PRNCPL_ID", principalList, String.class);
779 
780             returnSql = whereClausePredicatePrefix +
781                     " DOC_HDR.DOC_HDR_ID = KREW_ACTN_TKN_T.DOC_HDR_ID and upper(KREW_ACTN_TKN_T.ACTN_CD) in ('" +
782                     KewApiConstants.ACTION_TAKEN_APPROVED_CD + "','" + KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD + "')" +
783                     " and " + crit.buildWhere();
784         }
785         return returnSql;
786     }
787 
788     public String getDocTypeFullNameWhereSql(DocumentSearchCriteria criteria, String whereClausePredicatePrefix) {
789         List<String> documentTypeNamesToSearch = new ArrayList<String>();
790         String primaryDocumentTypeName = criteria.getDocumentTypeName();
791         if (StringUtils.isNotBlank(primaryDocumentTypeName)) {
792             documentTypeNamesToSearch.add(primaryDocumentTypeName);
793         }
794         documentTypeNamesToSearch.addAll(criteria.getAdditionalDocumentTypeNames());
795         StringBuilder returnSql = new StringBuilder("");
796         if (CollectionUtils.isNotEmpty(documentTypeNamesToSearch)) {
797             int index = 0;
798             for (String documentTypeName : documentTypeNamesToSearch) {
799                 if (StringUtils.isNotBlank(documentTypeName)) {
800                     String clause = index++ == 0 ? "" : " or ";
801                     DocumentTypeService docSrv = KEWServiceLocator.getDocumentTypeService();
802                     DocumentType docType = docSrv.findByNameCaseInsensitive(documentTypeName.trim());
803                     if (docType != null) {
804                         if (documentTypeName.contains("*") || documentTypeName.contains("%")) {
805                             addDocumentTypeLikeNameToSearchOn(returnSql, documentTypeName.trim(), clause);
806                         } else {
807                             addDocumentTypeNameToSearchOn(returnSql, documentTypeName.trim(), clause);
808                         }
809                         if (docType.getChildrenDocTypes() != null) {
810                             addChildDocumentTypes(returnSql, docType.getChildrenDocTypes());
811                         }
812                     } else{
813                         addDocumentTypeLikeNameToSearchOn(returnSql, documentTypeName.trim(), clause);
814                     }
815                 }
816             }
817         }
818         if (returnSql.length() > 0) {
819             returnSql.insert(0, "(");
820             returnSql.insert(0, whereClausePredicatePrefix);
821             returnSql.append(")");
822         }
823         return returnSql.toString();
824     }
825 
826     public void addChildDocumentTypes(StringBuilder whereSql, Collection<DocumentType> childDocumentTypes) {
827         for (DocumentType child : childDocumentTypes) {
828             addDocumentTypeNameToSearchOn(whereSql, child.getName());
829             addChildDocumentTypes(whereSql, child.getChildrenDocTypes());
830         }
831     }
832 
833     public void addDocumentTypeNameToSearchOn(StringBuilder whereSql, String documentTypeName) {
834         this.addDocumentTypeNameToSearchOn(whereSql, documentTypeName, " or ");
835     }
836 
837     public void addDocumentTypeNameToSearchOn(StringBuilder whereSql, String documentTypeName, String clause) {
838         whereSql.append(clause).append("upper(DOC1.DOC_TYP_NM) = '" + documentTypeName.toUpperCase() + "'");
839     }
840     public void addDocumentTypeLikeNameToSearchOn(StringBuilder whereSql, String documentTypeName, String clause) {
841         documentTypeName = documentTypeName.replace('*', '%');
842         whereSql.append(clause).append(" upper(DOC1.DOC_TYP_NM) LIKE '" + documentTypeName.toUpperCase() + "'");
843     }
844 
845     public String getDocRouteNodeSql(String documentTypeFullName, String routeNodeName, RouteNodeLookupLogic docRouteLevelLogic, String whereClausePredicatePrefix) {
846         // -1 is the default 'blank' choice from the route node drop down a number is used because the ojb RouteNode object is used to
847         // render the node choices on the form.
848         String returnSql = "";
849         if (StringUtils.isNotBlank(routeNodeName)) {
850             if (docRouteLevelLogic == null) {
851                 docRouteLevelLogic = RouteNodeLookupLogic.EXACTLY;
852             }
853             StringBuilder routeNodeCriteria = new StringBuilder("and " + ROUTE_NODE_TABLE + ".NM ");
854             if (RouteNodeLookupLogic.EXACTLY == docRouteLevelLogic) {
855         		routeNodeCriteria.append("= '" + getDbPlatform().escapeString(routeNodeName) + "' ");
856             } else {
857                 routeNodeCriteria.append("in (");
858                 // below buffer used to facilitate the addition of the string ", " to separate out route node names
859                 StringBuilder routeNodeInCriteria = new StringBuilder();
860                 boolean foundSpecifiedNode = false;
861                 List<RouteNode> routeNodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(getValidDocumentType(documentTypeFullName), true);
862                 for (RouteNode routeNode : routeNodes) {
863                     if (routeNodeName.equals(routeNode.getRouteNodeName())) {
864                         // current node is specified node so we ignore it outside of the boolean below
865                         foundSpecifiedNode = true;
866                         continue;
867                     }
868                     // below logic should be to add the current node to the criteria if we haven't found the specified node
869                     // and the logic qualifier is 'route nodes before specified'... or we have found the specified node and
870                     // the logic qualifier is 'route nodes after specified'
871                     if ( (!foundSpecifiedNode && RouteNodeLookupLogic.BEFORE == docRouteLevelLogic) ||
872                          (foundSpecifiedNode && RouteNodeLookupLogic.AFTER == docRouteLevelLogic) ) {
873                         if (routeNodeInCriteria.length() > 0) {
874                             routeNodeInCriteria.append(", ");
875                         }
876                         routeNodeInCriteria.append("'" + routeNode.getRouteNodeName() + "'");
877                     }
878                 }
879                 if (routeNodeInCriteria.length() > 0) {
880                     routeNodeCriteria.append(routeNodeInCriteria);
881                 } else {
882                     routeNodeCriteria.append("''");
883                 }
884                 routeNodeCriteria.append(") ");
885             }
886             returnSql = whereClausePredicatePrefix + "DOC_HDR.DOC_HDR_ID = " + ROUTE_NODE_INST_TABLE + ".DOC_HDR_ID and " + ROUTE_NODE_INST_TABLE + ".RTE_NODE_ID = " + ROUTE_NODE_TABLE + ".RTE_NODE_ID and " + ROUTE_NODE_INST_TABLE + ".ACTV_IND = 1 " + routeNodeCriteria.toString() + " ";
887         }
888         return returnSql;
889     }
890 
891     public String getDocumentStatusSql(List<DocumentStatus> documentStatuses, List<DocumentStatusCategory> categories, String whereClausePredicatePrefix) {
892         if (CollectionUtils.isEmpty(documentStatuses) && CollectionUtils.isEmpty(categories)) {
893             return whereClausePredicatePrefix + "DOC_HDR.DOC_HDR_STAT_CD != '" + DocumentStatus.INITIATED.getCode() + "'";
894         } else {
895             // include all given document statuses
896             Set<DocumentStatus> statusesToInclude = new HashSet<DocumentStatus>(documentStatuses);
897 
898             // add all statuses from each category
899             for (DocumentStatusCategory category : categories) {
900                 Set<DocumentStatus> categoryStatuses = DocumentStatus.getStatusesForCategory(category);
901                 statusesToInclude.addAll(categoryStatuses);
902             }
903 
904             Set<String> statusCodes = new HashSet<String>();
905             for (DocumentStatus statusToInclude : statusesToInclude) {
906                 statusCodes.add("'" + getDbPlatform().escapeString(statusToInclude.getCode()) + "'");
907             }
908             return whereClausePredicatePrefix + " DOC_HDR.DOC_HDR_STAT_CD in (" + StringUtils.join(statusCodes, ", ") +")";
909         }
910     }
911 
912     /**
913      * This method generates the where clause fragment related to Application Document Status.
914      * If the Status values only are defined, search for the appDocStatus value in the route header.
915      * If either the transition from/to dates are defined, search agains the status transition history.
916      */
917     public String getAppDocStatusesSql(List<String> appDocStatuses, String whereClausePredicatePrefix, int statusTransitionWhereClauseLength) {
918         if (CollectionUtils.isEmpty(appDocStatuses)) {
919             return "";
920         } else {
921             String inList = buildAppDocStatusInList(appDocStatuses);
922 
923             if (statusTransitionWhereClauseLength > 0){
924                 return whereClausePredicatePrefix + " STAT_TRAN.APP_DOC_STAT_TO" + inList;
925             } else {
926                 return whereClausePredicatePrefix + " DOC_HDR.APP_DOC_STAT" + inList;
927             }
928         }
929     }
930 
931     private String buildAppDocStatusInList(List<String> appDocStatuses) {
932         StringBuilder sql = new StringBuilder(" IN (");
933 
934         boolean first = true;
935         for (String appDocStatus : appDocStatuses) {
936             // commas before each element except the first one
937             if (first) {
938                 first = false;
939             } else {
940                 sql.append(",");
941             }
942 
943             sql.append("'");
944             sql.append(getDbPlatform().escapeString(appDocStatus.trim()));
945             sql.append("'");
946         }
947 
948         sql.append(")");
949 
950         return sql.toString();
951     }
952 
953     public String getGeneratedPredicatePrefix(int whereClauseSize) {
954         return (whereClauseSize > 0) ? " and " : " where ";
955     }
956 
957     public String establishDateString(DateTime fromDate, DateTime toDate, String tableName, String tableAlias, String colName, String whereStatementClause) {
958 
959         String fromDateValue = null;
960         if (fromDate != null) {
961             fromDateValue = CoreApiServiceLocator.getDateTimeService().toDateString(fromDate.toDate());
962         }
963 
964         String toDateValue = null;
965         if (toDate != null) {
966             toDateValue = CoreApiServiceLocator.getDateTimeService().toDateString(toDate.toDate());
967             toDateValue += " 23:59:59";
968         }
969 
970         String searchValue = null;
971         if (fromDateValue != null && toDateValue != null) {
972             searchValue = fromDateValue + " .. " + toDateValue;
973         } else if (fromDateValue != null) {
974             searchValue = ">= " + fromDateValue;
975         } else if (toDateValue != null) {
976             searchValue = "<= " + toDateValue;
977         } else {
978             return "";
979         }
980 
981         Criteria crit = getSqlBuilder().createCriteria(colName, searchValue, tableName, tableAlias, java.sql.Date.class, true, true);
982         return new StringBuilder(whereStatementClause).append(crit.buildWhere()).toString();
983 
984     }
985 
986     public DatabasePlatform getDbPlatform() {
987         if (dbPlatform == null) {
988             dbPlatform = (DatabasePlatform) GlobalResourceLoader.getService(RiceConstants.DB_PLATFORM);
989         }
990         return dbPlatform;
991     }
992 
993     public SqlBuilder getSqlBuilder() {
994         if(sqlBuilder == null){
995             sqlBuilder = new SqlBuilder();
996             sqlBuilder.setDbPlatform(getDbPlatform());
997             sqlBuilder.setDateTimeService(CoreApiServiceLocator.getDateTimeService());
998         }
999         return this.sqlBuilder;
1000     }
1001 
1002     public void setSqlBuilder(SqlBuilder sqlBuilder) {
1003         this.sqlBuilder = sqlBuilder;
1004     }
1005 
1006     /**
1007      * A helper method for determining whether any searchable attributes are in use for the search.
1008      *
1009      * @return True if the search criteria contains at least one searchable attribute or the criteria's doc type name is
1010      * non-blank; false otherwise.
1011      */
1012     protected boolean isUsingAtLeastOneSearchAttribute(DocumentSearchCriteria criteria) {
1013         return criteria.getDocumentAttributeValues().size() > 0 || StringUtils.isNotBlank(criteria.getDocumentTypeName());
1014     }
1015 
1016 }