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