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