Coverage Report - org.kuali.student.common.dao.impl.AbstractSearchableCrudDaoImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractSearchableCrudDaoImpl
0%
0/136
0%
0/104
29
 
 1  
 /**
 2  
  * Copyright 2010 The Kuali Foundation Licensed under the
 3  
  * Educational Community License, Version 2.0 (the "License"); you may
 4  
  * not use this file except in compliance with the License. You may
 5  
  * obtain a copy of the License at
 6  
  *
 7  
  * http://www.osedu.org/licenses/ECL-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing,
 10  
  * software distributed under the License is distributed on an "AS IS"
 11  
  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 12  
  * or implied. See the License for the specific language governing
 13  
  * permissions and limitations under the License.
 14  
  */
 15  
 
 16  
 package org.kuali.student.common.dao.impl;
 17  
 
 18  
 import java.text.ParseException;
 19  
 import java.text.SimpleDateFormat;
 20  
 import java.util.ArrayList;
 21  
 import java.util.Collection;
 22  
 import java.util.List;
 23  
 import java.util.Map;
 24  
 import java.util.StringTokenizer;
 25  
 
 26  
 import javax.persistence.Query;
 27  
 
 28  
 import org.apache.log4j.Logger;
 29  
 import org.kuali.student.common.dao.SearchableDao;
 30  
 import org.kuali.student.common.search.dto.QueryParamInfo;
 31  
 import org.kuali.student.common.search.dto.ResultColumnInfo;
 32  
 import org.kuali.student.common.search.dto.SearchParam;
 33  
 import org.kuali.student.common.search.dto.SearchRequest;
 34  
 import org.kuali.student.common.search.dto.SearchResult;
 35  
 import org.kuali.student.common.search.dto.SearchResultCell;
 36  
 import org.kuali.student.common.search.dto.SearchResultRow;
 37  
 import org.kuali.student.common.search.dto.SearchTypeInfo;
 38  
 import org.kuali.student.common.search.dto.SortDirection;
 39  
 
 40  0
 public class AbstractSearchableCrudDaoImpl extends AbstractCrudDaoImpl
 41  
                 implements SearchableDao {
 42  0
         final Logger LOG = Logger.getLogger(AbstractSearchableCrudDaoImpl.class);
 43  0
     private static SimpleDateFormat df = new SimpleDateFormat("EEE MMM dd hh:mm:ss zzz yyyy");
 44  
 
 45  
         @Override
 46  
         public SearchResult search(SearchRequest searchRequest,        Map<String, String> queryMap, SearchTypeInfo searchTypeInfo) {
 47  0
                 String searchKey = searchRequest.getSearchKey();
 48  
                 
 49  0
                 boolean isNative = false;
 50  
                 
 51  
                 //retrieve the SELECT statement from search type definition
 52  0
                 String queryString = queryMap.get(searchKey);
 53  0
                 String optionalQueryString = "";
 54  0
                 if(null == queryString){
 55  0
                         LOG.error("No SQL query was found for searchKey:"+searchKey);
 56  
                 }
 57  
                 
 58  0
                 if(queryString.toUpperCase().startsWith("NATIVE:")){
 59  0
                         queryString = queryString.substring("NATIVE:".length());
 60  0
                         isNative = true;
 61  
                 }
 62  
                 
 63  
                 //add in optional
 64  0
                 List<SearchParam> searchParamsTemp = new ArrayList<SearchParam>(searchRequest.getParams());
 65  
                 // internalQueryParms is used only internally to know which parameters have to be set in the query
 66  0
                 List<SearchParam> internalQueryParms = new ArrayList<SearchParam>(searchRequest.getParams());
 67  0
                 for(SearchParam searchParam : searchParamsTemp){
 68  0
                         for(QueryParamInfo queryParam:searchTypeInfo.getSearchCriteriaTypeInfo().getQueryParams()){
 69  
                                 // check to see if optional param has any values set.
 70  0
                                 if(queryParam.isOptional()&&queryParam.getKey().equals(searchParam.getKey())&&searchParam.getValue()!=null){
 71  0
                                         if(!optionalQueryString.isEmpty()){
 72  0
                                                 optionalQueryString += " AND ";
 73  
                                         }
 74  
                                         
 75  
                                         //if optional query parameter has only a column name then create proper search expression
 76  0
                                         String condition = queryMap.get(searchParam.getKey());
 77  0
                                         if (condition.trim().startsWith("!!")) {
 78  0
                                             String substitutionType = condition.trim().substring("!!".length());
 79  
                                             // to detect queryMap value in the form of !!____ ___
 80  0
                                             if (condition.contains(" ")) {
 81  0
                                                 substitutionType = condition.substring("!!".length(), condition.indexOf(" "));
 82  
                                             }
 83  
                                             
 84  0
                                             if (substitutionType != null && substitutionType.equals("NUMBER_RANGE")) {
 85  0
                                                 String realCondition = condition.substring("!!".length() + substitutionType.length()).trim();
 86  0
                                                 String queryValue = (String)searchParam.getValue();
 87  
                                                 // if the query value is of the form n1 - n2
 88  0
                                                 if (queryValue != null && queryValue.trim().contains("-")) {
 89  0
                                                     StringTokenizer strTokenizer = new StringTokenizer(queryValue.trim(),"-");
 90  0
                                                     if (strTokenizer.hasMoreElements()) {
 91  0
                                                         String strNum1 = strTokenizer.nextToken().trim();
 92  0
                                                         String strNum2 = strTokenizer.nextToken().trim();
 93  0
                                                         optionalQueryString += 
 94  
                                                             realCondition +
 95  
                                                             " BETWEEN " + "'" + strNum1 + "'" + " AND " + "'" + strNum2 + "'";
 96  0
                                                         internalQueryParms.remove(searchParam);
 97  
                                                     }
 98  0
                                                 } else {
 99  
                                                     // the value is just one number
 100  0
                                                     optionalQueryString += realCondition + " = '" + queryValue + "'";
 101  0
                                                     internalQueryParms.remove(searchParam);
 102  
                                                 }
 103  
                                             }
 104  0
                                         } else if (condition.trim().contains(":")) {
 105  
                                             //this parameter is not entered by end user but rather it is set with a default context value        
 106  0
                                             String dataType = queryParam.getFieldDescriptor().getDataType();
 107  0
                                             if ((dataType != null) && "boolean".equals(dataType)) {
 108  0
                                                 optionalQueryString += queryMap.get(searchParam.getKey()).replace(":" + searchParam.getKey().replace(".", "_"), searchParam.getValue().toString());
 109  0
                                                 internalQueryParms.remove(searchParam);
 110  
                                             } else {                                            
 111  0
                                                 optionalQueryString += queryMap.get(searchParam.getKey());
 112  
                                             }                                                
 113  0
                                         } else {
 114  
                                                 //comparison should be case insensitive and should include wild card such that we match beginning of a text 
 115  
                                                 //and each word within text
 116  
                                                 //FIXME SQL injection can occur here - or NOT if we need to assemble SQL to cover various ways one can compare criteria to a text
 117  0
                                                 optionalQueryString += 
 118  
                                                         "(LOWER(" + queryMap.get(searchParam.getKey()) + ") LIKE LOWER('" + searchParam.getValue() + "') || '%' OR " +
 119  
                                                         "LOWER(" + queryMap.get(searchParam.getKey()) + ") LIKE '% ' || LOWER('" + searchParam.getValue() + "') || '%')"; 
 120  0
                                                 internalQueryParms.remove(searchParam);
 121  
                                         }
 122  0
                                 }
 123  
                         }
 124  
                 }
 125  
                 
 126  
                 //Add in the where clause or And clause if needed for the optional criteria
 127  0
                 if(!optionalQueryString.isEmpty()){
 128  0
                         if(!queryString.toUpperCase().contains(" WHERE ")){
 129  0
                                 queryString += " WHERE ";
 130  
                         }
 131  
                         else {
 132  0
                                 queryString += " AND ";
 133  
                         }
 134  
                 }
 135  
                 
 136  
                 //Do ordering
 137  0
                 String orderByClause = "";                
 138  0
                 if(!queryString.toUpperCase().contains("ORDER BY")&&searchRequest.getSortColumn()!=null){
 139  
                         //make sure the sort column is a real result column
 140  0
                         int i = 0;
 141  
                         
 142  
                         //Get an array of the jpql results
 143  0
                         int selectIndex = queryString.toLowerCase().indexOf("select")+"select".length();
 144  0
                         int fromIndex = queryString.toLowerCase().indexOf(" from ");
 145  
                         
 146  0
                         if (selectIndex >= 0 && fromIndex > selectIndex){
 147  0
                                 String[] jpqlResultColumns = queryString.substring(selectIndex, fromIndex).replaceAll("\\s", "").split(",");
 148  0
                                 for(ResultColumnInfo results : searchTypeInfo.getSearchResultTypeInfo().getResultColumns()){
 149  0
                                         if(results.getKey().equals(searchRequest.getSortColumn())){
 150  0
                                                 orderByClause = " ORDER BY "+jpqlResultColumns[i]+" ";
 151  0
                                                 if(searchRequest.getSortDirection()!=null && searchRequest.getSortDirection()==SortDirection.DESC){
 152  0
                                                         orderByClause += "DESC ";
 153  
                                                 }else{
 154  0
                                                         orderByClause += "ASC ";
 155  
                                                 }
 156  
                                         }
 157  0
                                         i++;
 158  
                                 }
 159  
                         }                        
 160  
                 }
 161  
                 
 162  
                 //Create the query
 163  0
                 String finalQueryString = queryString + optionalQueryString + orderByClause;
 164  
                 
 165  
                 //remove special characters and extra spaces
 166  
                 //finalQueryString = finalQueryString.replaceAll("[\n\r\t]", " ");
 167  
                 //finalQueryString = finalQueryString.replaceAll("\\s+", " ");
 168  
                 
 169  
                 Query query;
 170  0
                 if(isNative){
 171  0
                         LOG.info("Native Query:"+finalQueryString);
 172  0
                         query = em.createNativeQuery(finalQueryString);
 173  
                 }else{
 174  0
                         LOG.info("JPQL Query:"+finalQueryString);
 175  0
                         query = em.createQuery(finalQueryString);
 176  
                 }
 177  
                 
 178  
                 //Set the pagination information (eg. only return 25 rows starting at row 100)
 179  0
                 if(searchRequest.getStartAt()!=null){
 180  0
                         query.setFirstResult(searchRequest.getStartAt().intValue());
 181  
                 }
 182  0
                 if(searchRequest.getMaxResults()!=null){
 183  0
                         query.setMaxResults(searchRequest.getMaxResults().intValue());
 184  
                 }
 185  
                 
 186  
                 //replace all the "." notation with "_" since the "."s in the ids of the queries will cause problems with the jpql  
 187  0
                 for (SearchParam searchParam : internalQueryParms) {
 188  
                         // check to see if optional param has any values set.
 189  0
                         if (searchParam.getValue() != null) {
 190  0
                             List<QueryParamInfo> queryParams = searchTypeInfo.getSearchCriteriaTypeInfo().getQueryParams();
 191  0
                             String paramDataType = null;
 192  0
                             if (queryParams != null) {
 193  0
                                 for (QueryParamInfo queryParam : queryParams) {
 194  0
                                     if (queryParam.getKey() != null && queryParam.getKey().equals(searchParam.getKey())) {
 195  0
                                         paramDataType = queryParam.getFieldDescriptor().getDataType();
 196  
                                     }
 197  
                                 }
 198  
                             }
 199  
                             
 200  0
                 Object queryParamValue = null;
 201  0
                             if ("date".equals(paramDataType) && searchParam.getValue() instanceof String) {
 202  
                                 try {
 203  0
                         queryParamValue = df.parse((String)searchParam.getValue());
 204  0
                     } catch (ParseException e) {
 205  0
                         throw new RuntimeException("Failed to parse date value " + searchParam.getValue());
 206  0
                     }
 207  
                             } else {
 208  0
                                 queryParamValue = searchParam.getValue();
 209  
                             }
 210  
                             //Needed to get around Hibernate not supporting IN(:var) where var is null or an empty collection
 211  0
                             if((queryParamValue==null||queryParamValue instanceof Collection && ((Collection<?>)queryParamValue).isEmpty())&&"list".equals(paramDataType)){
 212  0
                                     queryParamValue = "";
 213  
                             }
 214  0
                             query.setParameter(searchParam.getKey().replace(".", "_"), queryParamValue);
 215  0
                         }
 216  
                 }
 217  
 
 218  
                 // Turn into results
 219  0
                 List<SearchResultRow> results = convertToResults(query.getResultList(),searchTypeInfo);
 220  
 
 221  0
                 SearchResult searchResult = new SearchResult();
 222  0
                 searchResult.setRows(results);
 223  0
                 searchResult.setSortColumn(searchRequest.getSortColumn());
 224  0
                 searchResult.setSortDirection(searchRequest.getSortDirection());
 225  0
                 searchResult.setStartAt(searchRequest.getStartAt());
 226  0
                 if(searchRequest.getNeededTotalResults()!=null && searchRequest.getNeededTotalResults()){
 227  
                         //Get count of total rows if needed
 228  0
                         String regex = "^\\s*[Ss][Ee][Ll][Ee][Cc][Tt]\\s+([^,\\s]+)(.|[\r\n])*?\\s+[Ff][Rr][Oo][Mm]\\s+";
 229  0
                         String replacement = "SELECT COUNT($1) FROM ";
 230  0
                         String countQueryString = (queryString + optionalQueryString).replaceFirst(regex, replacement);
 231  
 
 232  0
                         LOG.info("Executing query: "+countQueryString);
 233  
                         Query countQuery;
 234  0
                         if(isNative){
 235  0
                                 countQuery = em.createNativeQuery(countQueryString);
 236  
                         }else{
 237  0
                                 countQuery = em.createQuery(countQueryString);
 238  
                         }
 239  0
                         for (SearchParam searchParam : internalQueryParms) {
 240  0
                                 countQuery.setParameter(searchParam.getKey().replace(".", "_"), searchParam.getValue());
 241  
                         }
 242  0
                         Long totalResults = (Long) countQuery.getSingleResult();
 243  0
                         searchResult.setTotalResults(totalResults.intValue());
 244  
                 }
 245  
 
 246  0
                 return searchResult;
 247  
         }
 248  
         
 249  
         private List<SearchResultRow> convertToResults(List<?> queryResults,
 250  
                         SearchTypeInfo searchTypeInfo) {
 251  0
                 List<SearchResultRow> results = new ArrayList<SearchResultRow>();
 252  
 
 253  0
                 if(queryResults!=null){
 254  
                         //Copy the query results to a Result object
 255  0
                         for(Object queryResult:queryResults){
 256  0
                                 SearchResultRow result = new SearchResultRow();
 257  0
                                 int i=0;
 258  0
                                 for (ResultColumnInfo resultColumn : searchTypeInfo.getSearchResultTypeInfo().getResultColumns()) {
 259  
                         
 260  0
                                         SearchResultCell resultCell = new SearchResultCell();
 261  0
                                         resultCell.setKey(resultColumn.getKey());
 262  
                                         
 263  
                                         try {
 264  0
                                                 Object queryResultCell = null;
 265  0
                                                 if(queryResult.getClass().isArray()){
 266  0
                                                         queryResultCell = ((Object[])queryResult)[i];
 267  
                                                         
 268  
                                                 }else{
 269  0
                                                         queryResultCell = queryResult;
 270  
                                                 }
 271  
                                                 
 272  0
                                                 if (queryResultCell != null) {
 273  0
                                                         resultCell.setValue(queryResultCell.toString());                                                        
 274  
                                                 }
 275  
                                         
 276  0
                                         } catch (Exception e) {
 277  0
                                                 throw new RuntimeException("Error copying results from " + queryResult.toString(),e);
 278  0
                                         }
 279  
                                         
 280  0
                                         result.getCells().add(resultCell);
 281  0
                                         i++;
 282  0
                                 }
 283  0
                                 results.add(result);
 284  0
                         }
 285  
                 }
 286  0
                 return results;
 287  
         }
 288  
 
 289  
 }