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    }