Clover Coverage Report - Kuali Student 1.2-M4-SNAPSHOT (Aggregated)
Coverage timestamp: Wed Jul 20 2011 11:14:35 EDT
../../../../../../../img/srcFileCovDistChart2.png 40% of files have more coverage
226   513   90   15.07
70   370   0.4   5
15     6  
3    
 
  CrossSearchManager       Line # 56 191 0% 74 218 16.8% 0.16793893
  CrossSearchManager.SearchResultRowComparator       Line # 161 35 0% 16 49 0% 0.0
  CrossSearchManager.DataType       Line # 383 0 - 0 0 - -1.0
 
  (1)
 
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.search.service.impl;
17   
18    import java.text.SimpleDateFormat;
19    import java.util.ArrayList;
20    import java.util.Collections;
21    import java.util.Comparator;
22    import java.util.Date;
23    import java.util.HashMap;
24    import java.util.List;
25    import java.util.Map;
26   
27    import org.kuali.student.common.search.dto.CrossSearchTypeInfo;
28    import org.kuali.student.common.search.dto.JoinComparisonInfo;
29    import org.kuali.student.common.search.dto.JoinCriteriaInfo;
30    import org.kuali.student.common.search.dto.JoinResultMappingInfo;
31    import org.kuali.student.common.search.dto.SearchParam;
32    import org.kuali.student.common.search.dto.SearchRequest;
33    import org.kuali.student.common.search.dto.SearchResult;
34    import org.kuali.student.common.search.dto.SearchResultCell;
35    import org.kuali.student.common.search.dto.SearchResultRow;
36    import org.kuali.student.common.search.dto.SortDirection;
37    import org.kuali.student.common.search.dto.SubSearchInfo;
38    import org.kuali.student.common.search.dto.SubSearchParamMappingInfo;
39    import org.kuali.student.common.search.dto.JoinComparisonInfo.ComparisonType;
40    import org.kuali.student.common.search.dto.JoinCriteriaInfo.JoinType;
41    import org.kuali.student.common.search.service.SearchDispatcher;
42   
43    /**
44    * This still needs a few things
45    * 1 - no search meta(sort, pagination) is implemented
46    * 2 - a way to do subselects should be implemented to reduce the processing and sheer size of the unions
47    * (for example if searching for LO and the related CLU by the LO description, we need to match ALL CLUs
48    * with just the LOs that match, meaning if we had 1000 clus, and 10 LOs we would be comparing 10000 results)
49    *
50    *
51    */
52    /**
53    * @author Daniel Epstein
54    *
55    */
 
56    public class CrossSearchManager {
57    private SearchDispatcher searchDispatcher;
58   
 
59  1 toggle public SearchResult doCrossSearch(SearchRequest searchRequest, CrossSearchTypeInfo crossSearchType) {
60  1 SearchResult searchResult = new SearchResult();
61   
62  1 Map<String,SearchResult> subSearchResults = new HashMap<String,SearchResult>();
63   
64    //First perform all the subsearches
65  1 for(SubSearchInfo subSearch:crossSearchType.getSubSearches()){
66    //Map the parameters to the subsearch
67  2 SearchRequest subSearchRequest = new SearchRequest();
68   
69  2 subSearchRequest.setSearchKey(subSearch.getSearchkey());
70  2 subSearchRequest.setParams(new ArrayList<SearchParam>());
71   
72    //For each param mapping, map the paramvalue from the cross search to the sub search
73  2 for(SubSearchParamMappingInfo paramMapping:subSearch.getSubSearchParamMappings()){
74  0 for(SearchParam crossSearchParam:searchRequest.getParams()){
75  0 if(paramMapping.getCrossSearchParam().equals(crossSearchParam.getKey())){
76  0 SearchParam subSearchParam = new SearchParam();
77  0 subSearchParam.setKey(paramMapping.getSubSearchParam());
78  0 Object paramValue = crossSearchParam.getValue();
79  0 if(paramValue instanceof String){
80  0 subSearchParam.setValue((String)paramValue);
81  0 }else if(paramValue instanceof List<?>){
82  0 subSearchParam.setValue((List<String>)paramValue);
83    }
84  0 subSearchRequest.getParams().add(subSearchParam);
85    }
86    }
87    }
88  2 SearchResult subSearchResult = searchDispatcher.dispatchSearch(subSearchRequest);
89  2 subSearchResults.put(subSearch.getKey(), subSearchResult);
90    }
91   
92    //merge the subsearches together using the join rules
93  1 if(crossSearchType.getJoinCriteria().getComparisons().isEmpty()){
94    //If the root join has no criteria then do a simple union of rows
95  1 for(Map.Entry<String,SearchResult> subSearchResult:subSearchResults.entrySet()){
96  2 for(SearchResultRow row:subSearchResult.getValue().getRows()){
97  4 SearchResultRow mappedResult = mapResultRow(subSearchResult.getKey(),row,crossSearchType);
98  4 searchResult.getRows().add(mappedResult);
99    }
100    }
101    }else{
102    //merge the subsearches together using the join rules (this is in o^2 time which is bad)
103  0 List <Map<String,SearchResultRow>> allPermutations = unionOfAllRows(subSearchResults);
104   
105  0 for(Map<String,SearchResultRow> permutation:allPermutations){
106  0 if(meetsCriteria(permutation,crossSearchType,crossSearchType.getJoinCriteria())){
107  0 SearchResultRow mappedResult = mapResultRow(permutation,crossSearchType);
108  0 searchResult.getRows().add(mappedResult);
109    }
110    }
111    }
112  1 return metaFilter(searchResult,searchRequest);
113    }
114   
115   
116   
117   
118    /**
119    * @param searchResult
120    * @param searchRequest
121    * @return a sorted and paginated result
122    */
 
123  1 toggle private SearchResult metaFilter(SearchResult searchResult,
124    SearchRequest searchRequest) {
125   
126  1 searchResult.setTotalResults(searchResult.getRows().size());
127  1 final String sortColumn = searchRequest.getSortColumn();
128  1 final SortDirection sortDirection = searchRequest.getSortDirection();
129   
130    //Sort if we need to
131  1 if(sortColumn!=null){
132  0 Collections.sort(searchResult.getRows(), new SearchResultRowComparator(sortColumn,sortDirection));
133    }
134   
135   
136   
137    //Paginate if we need to
138  1 if(searchRequest.getMaxResults()!=null){
139  0 int fromIndex=0;
140  0 if(searchRequest.getStartAt()!=null){
141  0 fromIndex=searchRequest.getStartAt();
142    }
143  0 int toIndex = fromIndex+searchRequest.getMaxResults();
144  0 SearchResult pagedResult = new SearchResult();
145  0 for (int i=fromIndex; i <= toIndex; i++) {
146  0 if (!(searchResult.getRows().size() < i+1)) {
147  0 pagedResult.getRows().add(searchResult.getRows().get(i));
148    }
149    }
150   
151  0 searchResult = pagedResult;
152    }
153  1 return searchResult;
154    }
155   
156   
157    /**
158    * Compares two SearchResultRow rows with a given sort direction and column
159    *
160    */
 
161    private static class SearchResultRowComparator implements Comparator<SearchResultRow> {
162    private String sortColumn;
163    private SortDirection sortDirection;
164   
 
165  0 toggle public SearchResultRowComparator(String sortColumn,
166    SortDirection sortDirection) {
167  0 super();
168  0 this.sortColumn = sortColumn;
169  0 this.sortDirection = sortDirection;
170    }
171   
 
172  0 toggle @Override
173    public int compare(SearchResultRow r1, SearchResultRow r2) {
174  0 int compareResult = 0;
175   
176    //Pares out the cell values to compare
177  0 String v1=null;
178  0 String v2=null;
179  0 for(SearchResultCell c:r1.getCells()){
180  0 if(sortColumn.equals(c.getKey())){
181  0 v1=c.getValue();
182  0 break;
183    }
184    }
185  0 for(SearchResultCell c:r2.getCells()){
186  0 if(sortColumn.equals(c.getKey())){
187  0 v2=c.getValue();
188  0 break;
189    }
190    }
191   
192    //Compare the values wiuth the right type (SHould be done more efficiently
193  0 try{
194  0 Integer v1Integer = Integer.parseInt(v1);
195  0 Integer v2Integer = Integer.parseInt(v2);
196  0 compareResult = v1Integer.compareTo(v2Integer);
197    }catch(Exception e1){
198  0 if(v1!=null&&v2!=null&&("true".equals(v1.toLowerCase())||"false".equals(v1.toLowerCase()))&&
199    ("true".equals(v2.toLowerCase())||"false".equals(v2.toLowerCase()))){
200  0 Boolean v1Boolean = Boolean.parseBoolean(v1);
201  0 Boolean v2Boolean = Boolean.parseBoolean(v2);
202  0 compareResult = v1Boolean.compareTo(v2Boolean);
203    }else{
204  0 try{
205  0 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
206  0 Date v1Date = df.parse(v1);
207  0 Date v2Date = df.parse(v2);
208  0 compareResult = v1Date.compareTo(v2Date);
209    }catch(Exception e){
210  0 if(v1!=null && v2!=null){
211  0 compareResult = v1.compareTo(v2);
212  0 }else if(v2==null){
213  0 compareResult = 0;
214    }else{
215  0 compareResult = -1;
216    }
217    }
218    }
219    }
220   
221    //Sort reverse if order is descending
222  0 if(SortDirection.DESC.equals(sortDirection)){
223  0 return -1 * compareResult;
224    }
225  0 return compareResult;
226    }
227   
228    }
229   
230   
231   
232    /**
233    * Maps results from multiple searches into a single result row
234    *
235    * @param permutation
236    * @param crossSearchType
237    * @return a mapped SearchResultRow
238    */
 
239  0 toggle private SearchResultRow mapResultRow(
240    Map<String, SearchResultRow> permutation,
241    CrossSearchTypeInfo crossSearchType) {
242    //FIXME this is pretty inefficient to loop through everything... a map structure for the cells might be better
243  0 SearchResultRow resultRow = new SearchResultRow();
244  0 for(JoinResultMappingInfo resultMapping: crossSearchType.getJoinResultMappings()){
245  0 for(SearchResultCell cell: permutation.get(resultMapping.getSubSearchKey()).getCells()){
246  0 if(resultMapping.getSubSearchResultParam().equals(cell.getKey())){
247  0 SearchResultCell mappedCell = new SearchResultCell();
248  0 mappedCell.setKey(resultMapping.getResultParam());
249  0 mappedCell.setValue(cell.getValue());
250  0 resultRow.getCells().add(mappedCell);
251  0 break;//FIXME breaks are bad... but there is no map in the cells
252    }
253    }
254    }
255  0 return resultRow;
256   
257    }
258   
 
259  4 toggle private SearchResultRow mapResultRow(
260    String subSearchKey, SearchResultRow row,
261    CrossSearchTypeInfo crossSearchType) {
262  4 SearchResultRow resultRow = new SearchResultRow();
263   
264  4 for(JoinResultMappingInfo resultMapping: crossSearchType.getJoinResultMappings()){
265  16 if(subSearchKey.equals(resultMapping.getSubSearchKey())){
266  8 for(SearchResultCell cell: row.getCells()){
267  12 if(resultMapping.getSubSearchResultParam().equals(cell.getKey())){
268  8 SearchResultCell mappedCell = new SearchResultCell();
269  8 mappedCell.setKey(resultMapping.getResultParam());
270  8 mappedCell.setValue(cell.getValue());
271  8 resultRow.getCells().add(mappedCell);
272  8 break;//FIXME breaks are bad... but there is no map in the cells
273    }
274    }
275    }
276    }
277  4 return resultRow;
278    }
279    /**
280    * Checks each comparison of the join criteria and recursively checks through nested criteria.
281    * Short circuits for false 'AND' joins and true 'OR' joins
282    * @param permutation
283    * @param crossSearchType
284    * @param joinCriteria
285    * @return whether the criteria is met
286    */
 
287  0 toggle private boolean meetsCriteria(Map<String, SearchResultRow> permutation,
288    CrossSearchTypeInfo crossSearchType, JoinCriteriaInfo joinCriteria){
289   
290  0 JoinType joinType = joinCriteria.getJoinType();
291   
292    //Check actual comparisons
293  0 for(JoinComparisonInfo comparison:joinCriteria.getComparisons()){
294  0 SearchResultRow leftResultRow = permutation.get(comparison.getLeftHandSide().getSubSearchKey());
295  0 String leftResultValue = null;
296  0 if(leftResultRow!=null){
297  0 for(SearchResultCell cell: leftResultRow.getCells()){
298  0 if(comparison.getLeftHandSide().getParam().equals(cell.getKey())){
299  0 leftResultValue = cell.getValue();
300  0 break;//FIXME breaks are bad... but there is no map in the cells
301    }
302    }
303    }
304   
305  0 SearchResultRow rightResultRow = permutation.get(comparison.getRightHandSide().getSubSearchKey());
306  0 String rightResultValue = null;
307  0 if(rightResultRow!=null){
308  0 for(SearchResultCell cell: rightResultRow.getCells()){
309  0 if(comparison.getRightHandSide().getParam().equals(cell.getKey())){
310  0 rightResultValue = cell.getValue();
311  0 break;//FIXME breaks are bad... but there is no map in the cells
312    }
313    }
314    }
315   
316    //Get the compare type for the
317    //TODO get the types for the params!
318  0 if(leftResultValue==null||rightResultValue==null){
319  0 int i=0;i++;
320    }
321  0 if(compare(null, leftResultValue,rightResultValue,comparison.getType())){
322  0 if(JoinType.OR.equals(joinType)){
323  0 return true;
324    }
325    }else{
326  0 if(JoinType.AND.equals(joinType)){
327  0 return false;
328    }
329    }
330    }
331   
332    //Check all subcriteria next
333  0 for(JoinCriteriaInfo subCriteria: joinCriteria.getJoinCriteria()){
334  0 if(meetsCriteria(permutation, crossSearchType, subCriteria)){
335  0 if(JoinType.OR.equals(joinType)){
336  0 return true;
337    }
338    }else{
339  0 if(JoinType.AND.equals(joinType)){
340  0 return false;
341    }
342    }
343    }
344   
345  0 if(JoinType.AND.equals(joinType)){
346  0 return true;
347    }
348  0 if(JoinType.OR.equals(joinType)){
349  0 return false;
350    }
351   
352  0 return false;
353    }
354   
355    /**
356    * @param searchResults
357    * @return a list of all possible combinations of rows
358    */
 
359  0 toggle private List <Map<String,SearchResultRow>> unionOfAllRows(Map<String, SearchResult> searchResults){
360  0 List <Map<String,SearchResultRow>> r = new ArrayList<Map<String,SearchResultRow>>();
361  0 for(Map.Entry<String,SearchResult> x:searchResults.entrySet()){
362  0 List<Map<String,SearchResultRow>> t = new ArrayList<Map<String,SearchResultRow>>();
363  0 if(x.getValue()!=null&&x.getValue().getRows()!=null){
364  0 for(SearchResultRow y:x.getValue().getRows()){
365  0 for(Map<String,SearchResultRow> i:r){
366  0 Map<String,SearchResultRow> unions = new HashMap<String,SearchResultRow>();
367  0 unions.putAll(i);
368  0 unions.put(x.getKey(), y);
369  0 t.add(unions);
370    }
371  0 if(r.size()==0){
372  0 Map<String,SearchResultRow> unions = new HashMap<String,SearchResultRow>();
373  0 unions.put(x.getKey(), y);
374  0 t.add(unions);
375    }
376    }
377    }
378  0 r = t;
379    }
380  0 return r;
381    }
382   
 
383    private enum DataType{STRING,INT,BOOLEAN,DATE}
384   
385   
386   
 
387  0 toggle private boolean compare(DataType dataType, String left, String right,
388    ComparisonType type ){
389    //FIXME needs a handle to the result params data types here
390  0 try{
391  0 Integer leftInteger = Integer.parseInt(left);
392  0 Integer rightInteger = Integer.parseInt(right);
393  0 return compareInt(leftInteger,rightInteger,type);
394    }catch(Exception e){
395    }
396  0 try{
397  0 if(("true".equals(left.toLowerCase())||"false".equals(left.toLowerCase()))&&
398    ("true".equals(right.toLowerCase())||"false".equals(right.toLowerCase()))){
399  0 Boolean leftBoolean = Boolean.parseBoolean(left);
400  0 Boolean rightBoolean = Boolean.parseBoolean(right);
401  0 return compareBoolean(leftBoolean,rightBoolean,type);
402    }
403    }catch(Exception e){
404    }
405  0 try{
406  0 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
407  0 Date leftDate = df.parse(left);
408  0 Date rightDate = df.parse(right);
409  0 return compareDate(leftDate,rightDate,type);
410    }catch(Exception e){
411    }
412  0 return compareString(left,right,type);
413    // switch(dataType){
414    // case BOOLEAN:
415    // Boolean leftBoolean = new Boolean(left);
416    // Boolean rightBoolean = new Boolean(right);
417    // return compareBoolean(leftBoolean,rightBoolean,type);
418    // case DATE:
419    // SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
420    // Date leftDate = df.parse(left);
421    // Date rightDate = df.parse(right);
422    // return compareDate(leftDate,rightDate,type);
423    // case INT:
424    // Integer leftInteger = Integer.getInteger(left);
425    // Integer rightInteger = Integer.getInteger(right);
426    // return compareInt(leftInteger,rightInteger,type);
427    // case STRING:
428    // return compareString(left,right,type);
429    // }
430    // return false;
431    }
432   
 
433  0 toggle private boolean compareString(String left, String right, ComparisonType type) {
434  0 switch(type){
435  0 case EQUALS:
436  0 return left.equals(right);
437  0 case GREATERTHAN:
438  0 return left.compareTo(right) > 0;
439  0 case GREATERTHANEQUALS:
440  0 return left.compareTo(right) >= 0;
441  0 case LESSTHAN:
442  0 return left.compareTo(right) < 0;
443  0 case LESSTHANEQUALS:
444  0 return left.compareTo(right) <= 0;
445  0 case NOTEQUALS:
446  0 return !left.equals(right);
447    }
448  0 return false;
449    }
450   
 
451  0 toggle private boolean compareInt(Integer left, Integer right, ComparisonType type) {
452  0 switch(type){
453  0 case EQUALS:
454  0 return left.equals(right);
455  0 case GREATERTHAN:
456  0 return left.compareTo(right) > 0;
457  0 case GREATERTHANEQUALS:
458  0 return left.compareTo(right) >= 0;
459  0 case LESSTHAN:
460  0 return left.compareTo(right) < 0;
461  0 case LESSTHANEQUALS:
462  0 return left.compareTo(right) <= 0;
463  0 case NOTEQUALS:
464  0 return !left.equals(right);
465    }
466  0 return false;
467    }
468   
 
469  0 toggle private boolean compareDate(Date left, Date right, ComparisonType type) {
470  0 switch(type){
471  0 case EQUALS:
472  0 return left.equals(right);
473  0 case GREATERTHAN:
474  0 return left.compareTo(right) > 0;
475  0 case GREATERTHANEQUALS:
476  0 return left.compareTo(right) >= 0;
477  0 case LESSTHAN:
478  0 return left.compareTo(right) < 0;
479  0 case LESSTHANEQUALS:
480  0 return left.compareTo(right) <= 0;
481  0 case NOTEQUALS:
482  0 return !left.equals(right);
483    }
484  0 return false;
485    }
486   
 
487  0 toggle private boolean compareBoolean(Boolean left, Boolean right,
488    ComparisonType type) {
489  0 switch(type){
490  0 case EQUALS:
491  0 return left.equals(right);
492  0 case GREATERTHAN:
493  0 return left.compareTo(right) > 0;
494  0 case GREATERTHANEQUALS:
495  0 return left.compareTo(right) >= 0;
496  0 case LESSTHAN:
497  0 return left.compareTo(right) < 0;
498  0 case LESSTHANEQUALS:
499  0 return left.compareTo(right) <= 0;
500  0 case NOTEQUALS:
501  0 return !left.equals(right);
502    }
503  0 return false;
504    }
 
505  6 toggle public void setSearchDispatcher(SearchDispatcher searchDispatcher) {
506  6 this.searchDispatcher = searchDispatcher;
507    }
508   
 
509  0 toggle public SearchDispatcher getSearchDispatcher() {
510  0 return searchDispatcher;
511    }
512   
513    }