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