| 1 |  |   | 
  | 2 |  |   | 
  | 3 |  |   | 
  | 4 |  |   | 
  | 5 |  |   | 
  | 6 |  |   | 
  | 7 |  |   | 
  | 8 |  |   | 
  | 9 |  |   | 
  | 10 |  |   | 
  | 11 |  |   | 
  | 12 |  |   | 
  | 13 |  |   | 
  | 14 |  |   | 
  | 15 |  |   | 
  | 16 |  |  package org.kuali.student.common.dao.impl; | 
  | 17 |  |   | 
  | 18 |  |  import java.math.BigDecimal; | 
  | 19 |  |  import java.text.DateFormat; | 
  | 20 |  |  import java.text.ParseException; | 
  | 21 |  |  import java.text.SimpleDateFormat; | 
  | 22 |  |  import java.util.ArrayList; | 
  | 23 |  |  import java.util.Collection; | 
  | 24 |  |  import java.util.List; | 
  | 25 |  |  import java.util.Map; | 
  | 26 |  |  import java.util.StringTokenizer; | 
  | 27 |  |   | 
  | 28 |  |  import javax.persistence.Query; | 
  | 29 |  |   | 
  | 30 |  |  import org.apache.log4j.Logger; | 
  | 31 |  |  import org.kuali.student.common.dao.SearchableDao; | 
  | 32 |  |  import org.kuali.student.common.search.dto.QueryParamInfo; | 
  | 33 |  |  import org.kuali.student.common.search.dto.ResultColumnInfo; | 
  | 34 |  |  import org.kuali.student.common.search.dto.SearchParam; | 
  | 35 |  |  import org.kuali.student.common.search.dto.SearchRequest; | 
  | 36 |  |  import org.kuali.student.common.search.dto.SearchResult; | 
  | 37 |  |  import org.kuali.student.common.search.dto.SearchResultCell; | 
  | 38 |  |  import org.kuali.student.common.search.dto.SearchResultRow; | 
  | 39 |  |  import org.kuali.student.common.search.dto.SearchTypeInfo; | 
  | 40 |  |  import org.kuali.student.common.search.dto.SortDirection; | 
  | 41 |  |   | 
  | 42 | 0 |  public class AbstractSearchableCrudDaoImpl extends AbstractCrudDaoImpl | 
  | 43 |  |                  implements SearchableDao { | 
  | 44 | 0 |          final Logger LOG = Logger.getLogger(AbstractSearchableCrudDaoImpl.class); | 
  | 45 |  |           | 
  | 46 | 0 |          private static ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { | 
  | 47 |  |                  protected DateFormat initialValue() { | 
  | 48 | 0 |                          return new SimpleDateFormat("EEE MMM dd hh:mm:ss zzz yyyy"); | 
  | 49 |  |                  } | 
  | 50 |  |          }; | 
  | 51 |  |   | 
  | 52 |  |          @Override | 
  | 53 |  |          public SearchResult search(SearchRequest searchRequest,        Map<String, String> queryMap, SearchTypeInfo searchTypeInfo) { | 
  | 54 | 0 |                  String searchKey = searchRequest.getSearchKey(); | 
  | 55 |  |                   | 
  | 56 | 0 |                  boolean isNative = false; | 
  | 57 |  |                   | 
  | 58 |  |                   | 
  | 59 | 0 |                  String queryString = queryMap.get(searchKey); | 
  | 60 | 0 |                  String optionalQueryString = ""; | 
  | 61 | 0 |                  if(null == queryString){ | 
  | 62 | 0 |                          LOG.error("No SQL query was found for searchKey:"+searchKey); | 
  | 63 |  |                  } | 
  | 64 |  |                   | 
  | 65 | 0 |                  if(queryString.toUpperCase().startsWith("NATIVE:")){ | 
  | 66 | 0 |                          queryString = queryString.substring("NATIVE:".length()); | 
  | 67 | 0 |                          isNative = true; | 
  | 68 |  |                  } | 
  | 69 |  |                   | 
  | 70 |  |                   | 
  | 71 | 0 |                  List<SearchParam> searchParamsTemp = new ArrayList<SearchParam>(searchRequest.getParams()); | 
  | 72 |  |                   | 
  | 73 | 0 |                  List<SearchParam> internalQueryParms = new ArrayList<SearchParam>(searchRequest.getParams()); | 
  | 74 | 0 |                  for(SearchParam searchParam : searchParamsTemp){ | 
  | 75 | 0 |                          for(QueryParamInfo queryParam:searchTypeInfo.getSearchCriteriaTypeInfo().getQueryParams()){ | 
  | 76 |  |                                   | 
  | 77 | 0 |                                  if(queryParam.isOptional()&&queryParam.getKey().equals(searchParam.getKey())&&searchParam.getValue()!=null){ | 
  | 78 | 0 |                                          if(!optionalQueryString.isEmpty()){ | 
  | 79 | 0 |                                                  optionalQueryString += " AND "; | 
  | 80 |  |                                          } | 
  | 81 |  |                                           | 
  | 82 |  |                                           | 
  | 83 | 0 |                                          String condition = queryMap.get(searchParam.getKey()); | 
  | 84 | 0 |                                          if(condition==null){ | 
  | 85 | 0 |                                                  throw new RuntimeException("Optional Param "+searchParam.getKey()+" must have a queryMap definition"); | 
  | 86 |  |                                          } | 
  | 87 | 0 |                                          if (condition.trim().startsWith("!!")) { | 
  | 88 | 0 |                                              String substitutionType = condition.trim().substring("!!".length()); | 
  | 89 |  |                                               | 
  | 90 | 0 |                                              if (condition.contains(" ")) { | 
  | 91 | 0 |                                                  substitutionType = condition.substring("!!".length(), condition.indexOf(" ")); | 
  | 92 |  |                                              } | 
  | 93 |  |                                               | 
  | 94 | 0 |                                              if (substitutionType != null && substitutionType.equals("NUMBER_RANGE")) { | 
  | 95 | 0 |                                                  String realCondition = condition.substring("!!".length() + substitutionType.length()).trim(); | 
  | 96 | 0 |                                                  String queryValue = (String)searchParam.getValue(); | 
  | 97 |  |                                                   | 
  | 98 | 0 |                                                  if (queryValue != null && queryValue.trim().contains("-")) { | 
  | 99 | 0 |                                                      StringTokenizer strTokenizer = new StringTokenizer(queryValue.trim(),"-"); | 
  | 100 | 0 |                                                      if (strTokenizer.hasMoreElements()) { | 
  | 101 | 0 |                                                          String strNum1 = strTokenizer.nextToken().trim(); | 
  | 102 | 0 |                                                          String strNum2 = strTokenizer.nextToken().trim(); | 
  | 103 | 0 |                                                          optionalQueryString +=  | 
  | 104 |  |                                                              realCondition + | 
  | 105 |  |                                                              " BETWEEN " + "'" + strNum1 + "'" + " AND " + "'" + strNum2 + "'"; | 
  | 106 | 0 |                                                          internalQueryParms.remove(searchParam); | 
  | 107 |  |                                                      } | 
  | 108 | 0 |                                                  } else { | 
  | 109 |  |                                                       | 
  | 110 | 0 |                                                      optionalQueryString += realCondition + " = '" + queryValue + "'"; | 
  | 111 | 0 |                                                      internalQueryParms.remove(searchParam); | 
  | 112 |  |                                                  } | 
  | 113 |  |                                              } | 
  | 114 | 0 |                                          } else if (condition.trim().contains(":")) { | 
  | 115 |  |                                               | 
  | 116 | 0 |                                              String dataType = queryParam.getFieldDescriptor().getDataType(); | 
  | 117 | 0 |                                              if ((dataType != null) && "boolean".equals(dataType)) { | 
  | 118 | 0 |                                                  optionalQueryString += queryMap.get(searchParam.getKey()).replace(":" + searchParam.getKey().replace(".", "_"), searchParam.getValue().toString()); | 
  | 119 | 0 |                                                  internalQueryParms.remove(searchParam); | 
  | 120 |  |                                              } else {                                             | 
  | 121 | 0 |                                                  optionalQueryString += queryMap.get(searchParam.getKey()); | 
  | 122 |  |                                              }                                                 | 
  | 123 | 0 |                                          } else { | 
  | 124 |  |                                                   | 
  | 125 |  |                                                   | 
  | 126 |  |                                                   | 
  | 127 | 0 |                                                  optionalQueryString +=  | 
  | 128 |  |                                  "(LOWER(" + queryMap.get(searchParam.getKey()) + ") LIKE LOWER(:" | 
  | 129 |  |                                          + searchParam.getKey().replace(".", "_") + ") || '%' OR " + | 
  | 130 |  |                                          "LOWER(" + queryMap.get(searchParam.getKey()) + ") LIKE '% ' || LOWER(:" | 
  | 131 |  |                                          + searchParam.getKey().replace(".", "_") + ") || '%')"; | 
  | 132 |  |                                          } | 
  | 133 | 0 |                                  } | 
  | 134 |  |                          } | 
  | 135 |  |                  } | 
  | 136 |  |                   | 
  | 137 |  |                   | 
  | 138 | 0 |                  if(!optionalQueryString.isEmpty()){ | 
  | 139 | 0 |                          if(!queryString.toUpperCase().contains(" WHERE ")){ | 
  | 140 | 0 |                                  queryString += " WHERE "; | 
  | 141 |  |                          } | 
  | 142 |  |                          else { | 
  | 143 | 0 |                                  queryString += " AND "; | 
  | 144 |  |                          } | 
  | 145 |  |                  } | 
  | 146 |  |                   | 
  | 147 |  |                   | 
  | 148 | 0 |                  String orderByClause = "";                 | 
  | 149 | 0 |                  if(!queryString.toUpperCase().contains("ORDER BY")&&searchRequest.getSortColumn()!=null){ | 
  | 150 |  |                           | 
  | 151 | 0 |                          int i = 0; | 
  | 152 |  |                           | 
  | 153 |  |                           | 
  | 154 | 0 |                          int selectIndex = queryString.toLowerCase().indexOf("select")+"select".length(); | 
  | 155 | 0 |                          int fromIndex = queryString.toLowerCase().indexOf(" from "); | 
  | 156 |  |                           | 
  | 157 | 0 |                          if (selectIndex >= 0 && fromIndex > selectIndex){ | 
  | 158 | 0 |                                  String[] jpqlResultColumns = queryString.substring(selectIndex, fromIndex).replaceAll("\\s", "").split(","); | 
  | 159 | 0 |                                  for(ResultColumnInfo results : searchTypeInfo.getSearchResultTypeInfo().getResultColumns()){ | 
  | 160 | 0 |                                          if(results.getKey().equals(searchRequest.getSortColumn())){ | 
  | 161 | 0 |                          if(results.getDataType()!=null && "string".equals(results.getDataType().toLowerCase())){ | 
  | 162 | 0 |                                  orderByClause = " ORDER BY LOWER(" + jpqlResultColumns[i] + ") "; | 
  | 163 |  |                          }else{ | 
  | 164 |  |                                   | 
  | 165 | 0 |                                  orderByClause = " ORDER BY " + jpqlResultColumns[i] + " "; | 
  | 166 |  |                          } | 
  | 167 | 0 |                                                  if(searchRequest.getSortDirection()!=null && searchRequest.getSortDirection()==SortDirection.DESC){ | 
  | 168 | 0 |                                                          orderByClause += "DESC "; | 
  | 169 |  |                                                  }else{ | 
  | 170 | 0 |                                                          orderByClause += "ASC "; | 
  | 171 |  |                                                  } | 
  | 172 |  |                                          } | 
  | 173 | 0 |                                          i++; | 
  | 174 |  |                                  } | 
  | 175 |  |                          }                         | 
  | 176 |  |                  } | 
  | 177 |  |                   | 
  | 178 |  |                   | 
  | 179 | 0 |                  String finalQueryString = queryString + optionalQueryString + orderByClause; | 
  | 180 |  |                   | 
  | 181 |  |                   | 
  | 182 |  |                   | 
  | 183 |  |                   | 
  | 184 |  |                   | 
  | 185 |  |                  Query query; | 
  | 186 | 0 |                  if(isNative){ | 
  | 187 | 0 |                          LOG.info("Native Query:"+finalQueryString); | 
  | 188 | 0 |                          query = em.createNativeQuery(finalQueryString); | 
  | 189 |  |                  }else{ | 
  | 190 | 0 |                          LOG.info("JPQL Query:"+finalQueryString); | 
  | 191 | 0 |                          query = em.createQuery(finalQueryString); | 
  | 192 |  |                  } | 
  | 193 |  |                   | 
  | 194 |  |                   | 
  | 195 | 0 |                  if(searchRequest.getStartAt()!=null){ | 
  | 196 | 0 |                          query.setFirstResult(searchRequest.getStartAt().intValue()); | 
  | 197 |  |                  } | 
  | 198 | 0 |                  if(searchRequest.getMaxResults()!=null){ | 
  | 199 | 0 |                          query.setMaxResults(searchRequest.getMaxResults().intValue()); | 
  | 200 |  |                  } | 
  | 201 |  |                   | 
  | 202 |  |                   | 
  | 203 | 0 |                  for (SearchParam searchParam : internalQueryParms) { | 
  | 204 |  |                           | 
  | 205 | 0 |                          if (searchParam.getValue() != null) { | 
  | 206 | 0 |                              List<QueryParamInfo> queryParams = searchTypeInfo.getSearchCriteriaTypeInfo().getQueryParams(); | 
  | 207 | 0 |                              String paramDataType = null; | 
  | 208 | 0 |                              if (queryParams != null) { | 
  | 209 | 0 |                                  for (QueryParamInfo queryParam : queryParams) { | 
  | 210 | 0 |                                      if (queryParam.getKey() != null && queryParam.getKey().equals(searchParam.getKey())) { | 
  | 211 | 0 |                                          paramDataType = queryParam.getFieldDescriptor().getDataType(); | 
  | 212 |  |                                      } | 
  | 213 |  |                                  } | 
  | 214 |  |                              } | 
  | 215 |  |                               | 
  | 216 | 0 |                  Object queryParamValue = null; | 
  | 217 | 0 |                              if ("date".equals(paramDataType) && searchParam.getValue() instanceof String) { | 
  | 218 |  |                                  try { | 
  | 219 | 0 |                          queryParamValue = df.get().parse((String)searchParam.getValue()); | 
  | 220 | 0 |                      } catch (ParseException e) { | 
  | 221 | 0 |                          throw new RuntimeException("Failed to parse date value " + searchParam.getValue(),e); | 
  | 222 | 0 |                      } | 
  | 223 | 0 |                              } if ("long".equals(paramDataType)){ | 
  | 224 | 0 |                                      if(searchParam.getValue() instanceof String) { | 
  | 225 |  |                                      try{ | 
  | 226 | 0 |                                                  queryParamValue = Long.valueOf((String)searchParam.getValue()); | 
  | 227 | 0 |                                  } catch (NumberFormatException e) { | 
  | 228 | 0 |                                      throw new RuntimeException("Failed to parse date value " + searchParam.getValue(),e); | 
  | 229 | 0 |                                  } | 
  | 230 | 0 |                                      }else if(searchParam.getValue() instanceof Collection){ | 
  | 231 |  |                                              try{ | 
  | 232 | 0 |                                                      List<Long> longList = new ArrayList<Long>(); | 
  | 233 | 0 |                                                      if(searchParam.getValue()!=null){ | 
  | 234 | 0 |                                                              for(String value:(Collection<String>)searchParam.getValue()){ | 
  | 235 | 0 |                                                                      longList.add(Long.parseLong(value)); | 
  | 236 |  |                                                              } | 
  | 237 |  |                                                      } | 
  | 238 | 0 |                                                  queryParamValue = longList; | 
  | 239 | 0 |                                  } catch (NumberFormatException e) { | 
  | 240 | 0 |                                      throw new RuntimeException("Failed to parse date value " + searchParam.getValue(),e); | 
  | 241 | 0 |                                  } | 
  | 242 |  |                                      } | 
  | 243 |  |                              } else { | 
  | 244 | 0 |                                  queryParamValue = searchParam.getValue(); | 
  | 245 |  |                              } | 
  | 246 |  |                               | 
  | 247 | 0 |                              if((queryParamValue==null||queryParamValue instanceof Collection && ((Collection<?>)queryParamValue).isEmpty())&&"list".equals(paramDataType)){ | 
  | 248 | 0 |                                      queryParamValue = ""; | 
  | 249 |  |                              } | 
  | 250 | 0 |                              query.setParameter(searchParam.getKey().replace(".", "_"), queryParamValue); | 
  | 251 | 0 |                          } | 
  | 252 |  |                  } | 
  | 253 |  |   | 
  | 254 |  |                   | 
  | 255 | 0 |                  List<SearchResultRow> results = convertToResults(query.getResultList(),searchTypeInfo); | 
  | 256 |  |   | 
  | 257 | 0 |                  SearchResult searchResult = new SearchResult(); | 
  | 258 | 0 |                  searchResult.setRows(results); | 
  | 259 | 0 |                  searchResult.setSortColumn(searchRequest.getSortColumn()); | 
  | 260 | 0 |                  searchResult.setSortDirection(searchRequest.getSortDirection()); | 
  | 261 | 0 |                  searchResult.setStartAt(searchRequest.getStartAt()); | 
  | 262 | 0 |                  if(searchRequest.getNeededTotalResults()!=null && searchRequest.getNeededTotalResults()){ | 
  | 263 |  |                           | 
  | 264 | 0 |              String regex = "^\\s*[Ss][Ee][Ll][Ee][Cc][Tt]\\s+([^,\\s]+)(.|[\r\n])*?\\s+[Ff][Rr][Oo][Mm]\\s+"; | 
  | 265 | 0 |              String replacement = "SELECT COUNT(DISTINCT $1) FROM "; | 
  | 266 | 0 |              queryString = queryString.replaceAll("([Dd][Ii][Ss][Tt][Ii][Nn][Cc][Tt])", ""); | 
  | 267 | 0 |                          String countQueryString = (queryString + optionalQueryString).replaceFirst(regex, replacement); | 
  | 268 |  |   | 
  | 269 | 0 |                          LOG.info("Executing query: "+countQueryString); | 
  | 270 |  |                          Query countQuery; | 
  | 271 | 0 |                          if(isNative){ | 
  | 272 | 0 |                                  countQuery = em.createNativeQuery(countQueryString); | 
  | 273 |  |                          }else{ | 
  | 274 | 0 |                                  countQuery = em.createQuery(countQueryString); | 
  | 275 |  |                          } | 
  | 276 | 0 |                          for (SearchParam searchParam : internalQueryParms) { | 
  | 277 | 0 |                                  countQuery.setParameter(searchParam.getKey().replace(".", "_"), searchParam.getValue()); | 
  | 278 |  |                          } | 
  | 279 | 0 |              Integer totalRecords = 0; | 
  | 280 | 0 |              Object resultObject = countQuery.getSingleResult(); | 
  | 281 | 0 |              if (resultObject instanceof BigDecimal) { | 
  | 282 | 0 |                  totalRecords = ((BigDecimal) resultObject).intValue(); | 
  | 283 | 0 |              } else if (resultObject instanceof Long) { | 
  | 284 | 0 |                  totalRecords = ((Long) resultObject).intValue(); | 
  | 285 |  |              } | 
  | 286 | 0 |              searchResult.setTotalResults(totalRecords); | 
  | 287 |  |                  } | 
  | 288 |  |   | 
  | 289 | 0 |                  return searchResult; | 
  | 290 |  |          } | 
  | 291 |  |           | 
  | 292 |  |          private List<SearchResultRow> convertToResults(List<?> queryResults, | 
  | 293 |  |                          SearchTypeInfo searchTypeInfo) { | 
  | 294 | 0 |                  List<SearchResultRow> results = new ArrayList<SearchResultRow>(); | 
  | 295 |  |   | 
  | 296 | 0 |                  if(queryResults!=null){ | 
  | 297 |  |                           | 
  | 298 | 0 |                          for(Object queryResult:queryResults){ | 
  | 299 | 0 |                                  SearchResultRow result = new SearchResultRow(); | 
  | 300 | 0 |                                  int i=0; | 
  | 301 | 0 |                                  for (ResultColumnInfo resultColumn : searchTypeInfo.getSearchResultTypeInfo().getResultColumns()) { | 
  | 302 |  |                           | 
  | 303 | 0 |                                          SearchResultCell resultCell = new SearchResultCell(); | 
  | 304 | 0 |                                          resultCell.setKey(resultColumn.getKey()); | 
  | 305 |  |                                           | 
  | 306 |  |                                          try { | 
  | 307 | 0 |                                                  Object queryResultCell = null; | 
  | 308 | 0 |                                                  if(queryResult.getClass().isArray()){ | 
  | 309 | 0 |                                                          queryResultCell = ((Object[])queryResult)[i]; | 
  | 310 |  |                                                           | 
  | 311 |  |                                                  }else{ | 
  | 312 | 0 |                                                          queryResultCell = queryResult; | 
  | 313 |  |                                                  } | 
  | 314 |  |                                                   | 
  | 315 | 0 |                                                  if (queryResultCell != null) { | 
  | 316 | 0 |                                                          resultCell.setValue(queryResultCell.toString());                                                         | 
  | 317 |  |                                                  } | 
  | 318 |  |                                           | 
  | 319 | 0 |                                          } catch (Exception e) { | 
  | 320 | 0 |                                                  throw new RuntimeException("Error copying results from " + queryResult.toString(),e); | 
  | 321 | 0 |                                          } | 
  | 322 |  |                                           | 
  | 323 | 0 |                                          result.getCells().add(resultCell); | 
  | 324 | 0 |                                          i++; | 
  | 325 | 0 |                                  } | 
  | 326 | 0 |                                  results.add(result); | 
  | 327 | 0 |                          } | 
  | 328 |  |                  } | 
  | 329 | 0 |                  return results; | 
  | 330 |  |          } | 
  | 331 |  |   | 
  | 332 |  |  } |