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.docsearch.dao.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.uif.RemotableAttributeField;
020import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
021import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
022import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
023import org.kuali.rice.kew.impl.document.search.DocumentSearchGenerator;
024import org.kuali.rice.kew.docsearch.dao.DocumentSearchDAO;
025import org.kuali.rice.kew.api.KewApiConstants;
026import org.kuali.rice.kew.util.PerformanceLogger;
027import org.kuali.rice.krad.util.KRADConstants;
028import org.springframework.dao.DataAccessException;
029import org.springframework.jdbc.core.ConnectionCallback;
030import org.springframework.jdbc.core.JdbcTemplate;
031import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
032
033import javax.sql.DataSource;
034import java.sql.Connection;
035import java.sql.ResultSet;
036import java.sql.SQLException;
037import java.sql.Statement;
038import java.util.List;
039
040/**
041 * Spring JdbcTemplate implementation of DocumentSearchDAO
042 *
043 * @author Kuali Rice Team (rice.collab@kuali.org)
044 *
045 */
046public class DocumentSearchDAOJdbcImpl implements DocumentSearchDAO {
047
048    public static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentSearchDAOJdbcImpl.class);
049    private static final int DEFAULT_FETCH_MORE_ITERATION_LIMIT = 10;
050    
051    private DataSource dataSource;
052
053    public void setDataSource(DataSource dataSource) {
054        this.dataSource = new TransactionAwareDataSourceProxy(dataSource);
055    }
056
057    @Override
058    public DocumentSearchResults.Builder findDocuments(final DocumentSearchGenerator documentSearchGenerator, final DocumentSearchCriteria criteria, final boolean criteriaModified, final List<RemotableAttributeField> searchFields) {
059        final int maxResultCap = getMaxResultCap(criteria);
060        try {
061            final JdbcTemplate template = new JdbcTemplate(dataSource);
062
063            return template.execute(new ConnectionCallback<DocumentSearchResults.Builder>() {
064                @Override
065                public DocumentSearchResults.Builder doInConnection(final Connection con) throws SQLException {
066                    final Statement statement = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
067                    try {
068                        final int fetchIterationLimit = getFetchMoreIterationLimit();
069                        final int fetchLimit = fetchIterationLimit * maxResultCap;
070                        statement.setFetchSize(maxResultCap + 1);
071                        statement.setMaxRows(fetchLimit + 1);
072
073                        PerformanceLogger perfLog = new PerformanceLogger();
074                        String sql = documentSearchGenerator.generateSearchSql(criteria, searchFields);
075                        perfLog.log("Time to generate search sql from documentSearchGenerator class: " + documentSearchGenerator
076                                .getClass().getName(), true);
077                        LOG.info("Executing document search with statement max rows: " + statement.getMaxRows());
078                        LOG.info("Executing document search with statement fetch size: " + statement.getFetchSize());
079                        perfLog = new PerformanceLogger();
080                        final ResultSet rs = statement.executeQuery(sql);
081                        try {
082                            perfLog.log("Time to execute doc search database query.", true);
083                            final Statement searchAttributeStatement = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
084                            try {
085                                        return documentSearchGenerator.processResultSet(criteria, criteriaModified, searchAttributeStatement, rs, maxResultCap, fetchLimit);
086                            } finally {
087                                try {
088                                    searchAttributeStatement.close();
089                                } catch (SQLException e) {
090                                    LOG.warn("Could not close search attribute statement.");
091                                }
092                            }
093                        } finally {
094                            try {
095                                rs.close();
096                            } catch (SQLException e) {
097                                LOG.warn("Could not close result set.");
098                            }
099                        }
100                    } finally {
101                        try {
102                            statement.close();
103                        } catch (SQLException e) {
104                            LOG.warn("Could not close statement.");
105                        }
106                    }
107                }
108            });
109
110        } catch (DataAccessException dae) {
111            String errorMsg = "DataAccessException: " + dae.getMessage();
112            LOG.error("getList() " + errorMsg, dae);
113            throw new RuntimeException(errorMsg, dae);
114        } catch (Exception e) {
115            String errorMsg = "LookupException: " + e.getMessage();
116            LOG.error("getList() " + errorMsg, e);
117            throw new RuntimeException(errorMsg, e);
118        }
119    }
120
121    /**
122     * Returns the maximum number of results that should be returned from the document search.
123     *
124     * @param criteria the criteria in which to check for a max results value
125     * @return the maximum number of results that should be returned from a document search
126     */
127    public int getMaxResultCap(DocumentSearchCriteria criteria) {
128        int systemLimit = KewApiConstants.DOCUMENT_LOOKUP_DEFAULT_RESULT_CAP;
129        String resultCapValue = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.DOCUMENT_SEARCH_DETAIL_TYPE, KewApiConstants.DOC_SEARCH_RESULT_CAP);
130        if (StringUtils.isNotBlank(resultCapValue)) {
131            try {
132                int configuredLimit = Integer.parseInt(resultCapValue);
133                if (configuredLimit <= 0) {
134                    LOG.warn(KewApiConstants.DOC_SEARCH_RESULT_CAP + " was less than or equal to zero.  Please use a positive integer.");
135                } else {
136                    systemLimit = configuredLimit;
137                }
138            } catch (NumberFormatException e) {
139                LOG.warn(KewApiConstants.DOC_SEARCH_RESULT_CAP + " is not a valid number.  Value was " + resultCapValue + ".  Using default: " + KewApiConstants.DOCUMENT_LOOKUP_DEFAULT_RESULT_CAP);
140            }
141        }
142        int maxResults = systemLimit;
143        if (criteria.getMaxResults() != null) {
144            int criteriaLimit = criteria.getMaxResults().intValue();
145            if (criteriaLimit > systemLimit) {
146                LOG.warn("Result set cap of " + criteriaLimit + " is greater than system value of " + systemLimit);
147            } else {
148                if (criteriaLimit < 0) {
149                    LOG.warn("Criteria results limit was less than zero.");
150                    criteriaLimit = 0;
151                }
152                maxResults = criteriaLimit;
153            }
154        }
155        return maxResults;
156    }
157
158    public int getFetchMoreIterationLimit() {
159        int fetchMoreLimit = DEFAULT_FETCH_MORE_ITERATION_LIMIT;
160        String fetchMoreLimitValue = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.DOCUMENT_SEARCH_DETAIL_TYPE, KewApiConstants.DOC_SEARCH_FETCH_MORE_ITERATION_LIMIT);
161        if (!StringUtils.isBlank(fetchMoreLimitValue)) {
162            try {
163                fetchMoreLimit = Integer.parseInt(fetchMoreLimitValue);
164                if (fetchMoreLimit < 0) {
165                    LOG.warn(KewApiConstants.DOC_SEARCH_FETCH_MORE_ITERATION_LIMIT + " was less than zero.  Please use a value greater than or equal to zero.");
166                    fetchMoreLimit = DEFAULT_FETCH_MORE_ITERATION_LIMIT;
167                }
168            } catch (NumberFormatException e) {
169                LOG.warn(KewApiConstants.DOC_SEARCH_FETCH_MORE_ITERATION_LIMIT + " is not a valid number.  Value was " + fetchMoreLimitValue);
170            }
171        }
172        return fetchMoreLimit;
173    }
174
175}