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