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