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