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