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