View Javadoc

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  package org.kuali.student.lum.ui;
16  
17  import java.util.ArrayList;
18  import java.util.Arrays;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.kuali.student.r1.common.assembly.data.Data;
23  import org.kuali.student.r1.common.assembly.data.LookupMetadata;
24  import org.kuali.student.r1.common.assembly.data.LookupParamMetadata;
25  import org.kuali.student.r1.common.assembly.data.LookupResultMetadata;
26  import org.kuali.student.r1.common.assembly.data.Metadata;
27  import org.kuali.student.r1.core.personsearch.service.impl.QuickViewByGivenNameSearchTypeCreator;
28  import org.kuali.student.r2.core.search.dto.*;
29  import org.springframework.context.ConfigurableApplicationContext;
30  import org.springframework.context.support.ClassPathXmlApplicationContext;
31  
32  public class MetadataServiceDictionaryValidator {
33  
34  	private SearchTypeInfo getSearchTypeInfo(String searchType) {
35  		return this.getSearchInfoTypeMap().get(searchType);
36  	}
37  
38  	private Map<String, SearchTypeInfo> searchInfoTypeMap = null;
39  	
40  	String[] excludingSearchTypeIDs = { "atp.search.atpSeasonTypes", "atp.search.atpDurationTypes" };
41  
42  	@SuppressWarnings("unchecked")
43  	private Map<String, SearchTypeInfo> getSearchInfoTypeMap() {
44  		if (this.searchInfoTypeMap != null) {
45  			return this.searchInfoTypeMap;
46  		}
47  		String[] searchConfigFiles = { "lu", "lo", "lrc", "proposal", "organization", "atp", "em" };
48  
49          List<String> configFilesWithCompleteClasspath = new ArrayList<String>(searchConfigFiles.length);
50          for (String searchConfigFile : Arrays.asList(searchConfigFiles)) {
51              configFilesWithCompleteClasspath.add("classpath:" + searchConfigFile + "-search-config.xml");
52          }
53  
54          ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext(
55                  configFilesWithCompleteClasspath.toArray(new String[configFilesWithCompleteClasspath.size()]));
56  
57          if (searchInfoTypeMap == null) {
58              searchInfoTypeMap = ac.getBeansOfType(SearchTypeInfo.class);
59          } else {
60              searchInfoTypeMap.putAll(ac.getBeansOfType(SearchTypeInfo.class));
61          }
62          ac.close();
63  
64  		SearchTypeInfo personSearchType = new QuickViewByGivenNameSearchTypeCreator().get();
65  		searchInfoTypeMap.put(personSearchType.getKey(), personSearchType);
66  
67  		return searchInfoTypeMap;
68  	}
69  
70  	public List<String> validateMetadata(Metadata md, String name, String type) {
71  		List<String> errors = new ArrayList<String>();
72  		if (md.getInitialLookup() != null && md.getInitialLookup().getSearchTypeId() != null) {
73  			errors.addAll(validateLookup(md.getInitialLookup(), name, type,
74  					"initial"));
75  		}
76  		if (md.getAdditionalLookups() != null) {
77  			for (LookupMetadata lookup : md.getAdditionalLookups()) {
78  				errors.addAll(validateLookup(lookup, name, type, "additional"));
79  			}
80  		}
81  		if (md.getDataType().equals(Data.DataType.DATA)) {
82  			if (md.getProperties() == null) {
83  				errors
84  						.add(buildErrorPrefix1(name, type)
85  								+ " is of type DATA but it has null for it's properties");
86  			} else if (md.getProperties().size() == 0) {
87  				errors.add(buildErrorPrefix1(name, type)
88  						+ " is of type DATA but it has no properties");
89  			}
90  		}
91  
92  		if (md.getProperties() != null && md.getProperties().size() != 0) {
93  			if (!(md.getDataType().equals(Data.DataType.DATA) || md
94  					.getDataType().equals(Data.DataType.LIST))) {
95  				errors
96  						.add(buildErrorPrefix1(name, type)
97  								+ " is NOT of type DATA or LIST but it does have properties");
98  			}
99  			for (String key : md.getProperties().keySet()) {
100 				Metadata childMd = md.getProperties().get(key);
101 				errors.addAll(this.validateMetadata(childMd, name + "." + key,
102 						type));
103 			}
104 		}
105 		return errors;
106 	}
107 
108 	private List<String> validateLookup(LookupMetadata lookup, String name,
109 			String type, String lookupType) {
110 		System.out.println("Validating lookup " + name + "(" + type + ") "
111 				+ lookupType);
112 		List<String> errors = new ArrayList<String>();
113 		// Check excluded searchTypeIDs - these id's are not errors, but they don't line up with the DTO's
114 		if (ignoreExcludedSearchIDs(lookup.getSearchTypeId())) {
115 			return errors;			
116 		}
117 		//
118 		SearchTypeInfo st = getSearchTypeInfo(lookup.getSearchTypeId());
119 		if (st == null) {
120 			errors.add(buildErrorPrefix3(lookup, name, type, lookupType)
121 					+ " that has an underlying search type "
122 					+ lookup.getSearchTypeId() + " that does not exist");
123 			return errors;
124 		}
125 		if (lookup.getResultDisplayKey() != null) {
126 			String key = lookup.getResultDisplayKey().trim();
127 			if (!key.equals("")) {
128 				ResultColumnInfo rc = findResultColumn(st, key);
129 				if (rc == null) {
130 					errors
131 							.add(buildErrorPrefix3(lookup, name, type,
132 									lookupType)
133 									+ " that has a result display column "
134 									+ key
135 									+ " that does not exist in the underlying search "
136 									+ lookup.getSearchTypeId());
137 				}
138 			}
139 		}
140 		if (lookup.getResultReturnKey() != null) {
141 			String key = lookup.getResultReturnKey().trim();
142 			if ((!key.equals(""))) {
143 				ResultColumnInfo rc = findResultColumn(st, key);
144 				if (rc == null) {
145 					errors
146 							.add(buildErrorPrefix3(lookup, name, type,
147 									lookupType)
148 									+ " that has a result return key "
149 									+ key
150 									+ " that does not exist in the underlying search "
151 									+ lookup.getSearchTypeId());
152 				}
153 			}
154 		}
155 		if (lookup.getResultSortKey() != null) {
156 			String key = lookup.getResultSortKey().trim();
157 			if (!key.equals("")) {
158 				ResultColumnInfo rc = findResultColumn(st, key);
159 				if (rc == null) {
160 					errors
161 							.add(buildErrorPrefix3(lookup, name, type,
162 									lookupType)
163 									+ " that has a result sort key "
164 									+ key
165 									+ " that does not exist in the underlying search "
166 									+ lookup.getSearchTypeId());
167 				}
168 			}
169 		}
170 		// check params
171 		for (LookupParamMetadata param : lookup.getParams()) {
172 			QueryParamInfo qp = findQueryParam(st, param.getKey());
173 			if (qp == null && !(st instanceof CrossSearchTypeInfo)) {
174 				//Report error for missing query param def, but not for cross searches 
175 				//since cross search params may not be defined in same config file
176 				errors.add(buildErrorPrefix3(lookup, name, type, lookupType)
177 						+ " that has a parameter " + param.getKey()
178 						+ " that does not exist in the underlying search "
179 						+ lookup.getSearchTypeId());
180 				continue;
181 			}
182 			
183 			if (qp != null && !dataTypeMatches(qp.getFieldDescriptor().getDataType(), param.getDataType())) {
184 				//Verify parameter data type
185 				errors.add(buildErrorPrefix3(lookup, name, type, lookupType)
186 						+ " that has a parameter "
187 						+ param.getKey()
188 						+ " who's datatype does not match the underlying parameter "
189 						+ qp.getFieldDescriptor().getDataType()
190 						+ " vs. " + param.getDataType());
191             }
192 		}
193 		// check results
194 		for (LookupResultMetadata result : lookup.getResults()) {
195 			ResultColumnInfo rc = findResultColumn(st, result.getKey());
196 			if (rc == null) {
197 				errors.add(buildErrorPrefix3(lookup, name, type, lookupType)
198 						+ " that has a result column " + result.getKey()
199 						+ " that does not exist in the underlying search "
200 						+ lookup.getSearchTypeId());
201 				continue;
202 			}
203 			if (!dataTypeMatches(rc.getDataType(), result.getDataType())) {
204 				errors
205 						.add(buildErrorPrefix3(lookup, name, type, lookupType)
206 								+ " that has a result column "
207 								+ result.getKey()
208 								+ " who's datatype does not match the underlying result column "
209 								+ rc.getDataType() + " vs. "
210 								+ result.getDataType());
211             }
212 		}
213 		return errors;
214 	}
215 
216 	private boolean dataTypeMatches(String qp, Data.DataType dt) {
217 		if (dt == null) {
218 			dt = Data.DataType.STRING;
219 		}
220 		if (qp == null) {
221 			qp = "string";
222 		}
223 		switch (dt) {
224 		case STRING:
225             return qp.equalsIgnoreCase("string");
226         case INTEGER:
227             return qp.equalsIgnoreCase("int");
228         case LONG:
229 		case FLOAT:
230 		case DOUBLE:
231 		case BOOLEAN:
232             return qp.equalsIgnoreCase("boolean");
233         case DATE:
234 		case TRUNCATED_DATE:
235             return qp.equalsIgnoreCase("date") || qp.equalsIgnoreCase("dateTime");
236         case DATA:
237             return qp.equalsIgnoreCase("complex");
238         case LIST:
239 			return true;
240 		}
241 		return true;
242 	}
243 
244 	private ResultColumnInfo findResultColumn(SearchTypeInfo st, String paramKey) {
245 		for (ResultColumnInfo rc : st.getSearchResultTypeInfo()
246 				.getResultColumns()) {
247 			if (rc.getKey().equals(paramKey)) {
248 				return rc;
249 			}
250 		}
251 		if (st instanceof CrossSearchTypeInfo) {
252 			// System.out.println (
253 			// "CROSS SEARCH!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
254 			CrossSearchTypeInfo cst = (CrossSearchTypeInfo) st;
255 			if (cst.getJoinResultMappings() != null) {
256 				for (JoinResultMappingInfo jrm : cst.getJoinResultMappings()) {
257 					if (jrm.getResultParam().equalsIgnoreCase(paramKey)) {
258 						for (SubSearchInfo ss : cst.getSubSearches()) {
259 							if (ss.getKey().equalsIgnoreCase(
260 									jrm.getSubSearchKey())) {
261 								SearchTypeInfo subSearchType = this
262 										.getSearchTypeInfo(ss.getSearchkey());
263 								if (subSearchType == null) {
264 									return null;
265 								}
266 								ResultColumnInfo rc = findResultColumn(
267 										subSearchType, jrm
268 												.getSubSearchResultParam());
269 								if (rc == null) {
270 									throw new RuntimeException(
271 											"Cross-Search "
272 													+ st.getKey()
273 													+ " is not configured properly "
274 													+ jrm
275 															.getSubSearchResultParam()
276 													+ " is not defined as a result in the subSearchTyp e"
277 													+ ss.getSearchkey());
278 								}
279 							}
280 						}
281 					}
282 				}
283 			}
284 		}
285 		return null;
286 	}
287 
288 	private QueryParamInfo findQueryParam(SearchTypeInfo st, String paramKey) {
289 		for (QueryParamInfo qp : st.getSearchCriteriaTypeInfo()
290 				.getQueryParams()) {
291 			if (qp.getKey().equals(paramKey)) {
292 				return qp;
293 			}
294 		}
295 		return null;
296 	}
297 
298 	private String buildErrorPrefix3(LookupMetadata lookup, String name,
299 			String type, String lookupType) {
300 		return buildErrorPrefix2(name, type, lookupType) + ": "
301 				+ lookup.getId();
302 	}
303 
304 	private String buildErrorPrefix2(String name, String type, String lookupType) {
305 		String msg = buildErrorPrefix1(name, type);
306 		msg += " has an " + lookupType + " lookup ";
307 		return msg;
308 	}
309 
310 	private String buildErrorPrefix1(String name, String type) {
311 		String msg = name;
312 		if (type != null) {
313 			msg += " with type " + type;
314 		}
315 		// System.out.println ("buildErrorPrefix called for " + msg);
316 		// new RuntimeException ().printStackTrace ();
317 		return msg;
318 	}
319 
320 	private boolean ignoreExcludedSearchIDs(String searchID) {
321         for (String item : excludingSearchTypeIDs) {
322             if (item.equals(searchID)) {
323                 return true;
324             }
325         }
326 		return false;
327 		
328 	}
329 }