001    /**
002     * Copyright 2005-2012 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        protected int getMaxResultCap(DocumentSearchCriteria criteria) {
128            int maxResults = KewApiConstants.DOCUMENT_LOOKUP_DEFAULT_RESULT_CAP;
129            if (criteria.getMaxResults() != null) {
130                maxResults = criteria.getMaxResults().intValue();
131            }
132            String resultCapValue = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.DOCUMENT_SEARCH_DETAIL_TYPE, KewApiConstants.DOC_SEARCH_RESULT_CAP);
133            if (StringUtils.isNotBlank(resultCapValue)) {
134                try {
135                    Integer maxResultCap = Integer.parseInt(resultCapValue);
136                    if (maxResults > maxResultCap) {
137                        LOG.warn("Result set cap of " + maxResults + " is greater than parameter " + KewApiConstants.DOC_SEARCH_RESULT_CAP + " value of " + maxResultCap);
138                        maxResults = maxResultCap;
139                    } else if (maxResultCap <= 0) {
140                        LOG.warn(KewApiConstants.DOC_SEARCH_RESULT_CAP + " was less than or equal to zero.  Please use a positive integer.");
141                    }
142                } catch (NumberFormatException e) {
143                    LOG.warn(KewApiConstants.DOC_SEARCH_RESULT_CAP + " is not a valid number.  Value was " + resultCapValue);
144                }
145            }
146            return maxResults;
147        }
148    
149        protected int getFetchMoreIterationLimit() {
150            int fetchMoreLimit = DEFAULT_FETCH_MORE_ITERATION_LIMIT;
151            String fetchMoreLimitValue = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.DOCUMENT_SEARCH_DETAIL_TYPE, KewApiConstants.DOC_SEARCH_FETCH_MORE_ITERATION_LIMIT);
152            if (!StringUtils.isBlank(fetchMoreLimitValue)) {
153                try {
154                    fetchMoreLimit = Integer.parseInt(fetchMoreLimitValue);
155                    if (fetchMoreLimit < 0) {
156                        LOG.warn(KewApiConstants.DOC_SEARCH_FETCH_MORE_ITERATION_LIMIT + " was less than zero.  Please use a value greater than or equal to zero.");
157                        fetchMoreLimit = DEFAULT_FETCH_MORE_ITERATION_LIMIT;
158                    }
159                } catch (NumberFormatException e) {
160                    LOG.warn(KewApiConstants.DOC_SEARCH_FETCH_MORE_ITERATION_LIMIT + " is not a valid number.  Value was " + fetchMoreLimitValue);
161                }
162            }
163            return fetchMoreLimit;
164        }
165    
166    }