001 /** 002 * Copyright 2005-2013 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 */ 016 package org.kuali.rice.kew.docsearch.dao.impl; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.kuali.rice.core.api.uif.RemotableAttributeField; 020 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 021 import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria; 022 import org.kuali.rice.kew.api.document.search.DocumentSearchResults; 023 import org.kuali.rice.kew.impl.document.search.DocumentSearchGenerator; 024 import org.kuali.rice.kew.docsearch.dao.DocumentSearchDAO; 025 import org.kuali.rice.kew.api.KewApiConstants; 026 import org.kuali.rice.kew.util.PerformanceLogger; 027 import org.kuali.rice.krad.util.KRADConstants; 028 import org.springframework.dao.DataAccessException; 029 import org.springframework.jdbc.core.ConnectionCallback; 030 import org.springframework.jdbc.core.JdbcTemplate; 031 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; 032 033 import javax.sql.DataSource; 034 import java.sql.Connection; 035 import java.sql.ResultSet; 036 import java.sql.SQLException; 037 import java.sql.Statement; 038 import java.util.List; 039 040 /** 041 * Spring JdbcTemplate implementation of DocumentSearchDAO 042 * 043 * @author Kuali Rice Team (rice.collab@kuali.org) 044 * 045 */ 046 public 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 }