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  
16  package org.kuali.student.common.ui.client.application;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  
26  import org.kuali.student.r1.common.assembly.data.Metadata;
27  import org.kuali.student.r1.common.assembly.data.MetadataInterrogator;
28  import org.kuali.student.common.ui.client.configurable.mvc.FieldDescriptor;
29  import org.kuali.student.common.ui.client.mvc.Callback;
30  import org.kuali.student.common.ui.client.mvc.HasCrossConstraints;
31  import org.kuali.student.common.ui.client.security.SecurityContext;
32  import org.kuali.student.common.ui.client.service.ServerPropertiesRpcService;
33  import org.kuali.student.common.ui.client.service.ServerPropertiesRpcServiceAsync;
34  import org.kuali.student.common.ui.client.validator.ValidationMessageKeys;
35  import org.kuali.student.common.util.MessageUtils;
36  import org.kuali.student.r2.common.dto.ValidationResultInfo;
37  import org.kuali.student.r2.common.infc.ValidationResult.ErrorLevel;
38  import org.kuali.student.r2.common.messages.dto.MessageInfo;
39  
40  import com.google.gwt.core.client.GWT;
41  
42  /**
43   * The application contains information about who is currently logged in, the security context, and access
44   * to messages loaded from the message service in the app.  It provides and a static way to obtain this
45   * information across the entire app.
46   * 
47   * @author Kuali Student
48   *
49   */
50  public class ApplicationContext {
51  	private ServerPropertiesRpcServiceAsync serverPropertiesRpcService = GWT.create(ServerPropertiesRpcService.class);			
52  	private String version = "KS";
53  	
54  	private Map<String, Map<String, String>> messages = new HashMap<String, Map<String,String>>();
55  	private Map<String, String> flatMessages = new HashMap<String, String>();
56  	private List<MessageInfo> messagesList = new ArrayList<MessageInfo>();
57  	
58  	private SecurityContext securityContext;
59  	private String applicationContextUrl;
60  	
61  	//These maps are used to store query paths to their corresponding fieldDefinitions, and also whcih fields have cross constraints
62  	private String parentPath = "";
63  	private HashMap<String,HashMap<String,FieldDescriptor>> pathToFieldMapping = new HashMap<String,HashMap<String,FieldDescriptor>>();
64  	private HashMap<String,HashMap<String,HashSet<HasCrossConstraints>>> crossConstraints = new HashMap<String,HashMap<String,HashSet<HasCrossConstraints>>>();
65  	private List<ValidationResultInfo> validationWarnings = new ArrayList<ValidationResultInfo>();
66  
67  	/**
68  	 * This constructor should only be visible to the common application package. If ApplicationContext is 
69  	 * required outside this package do Application.getApplicationContext();
70  	 */
71  	protected ApplicationContext() {		
72  	}
73  
74  	/**
75  	 * This makes server side calls to initialize the application and application security context.
76  	 * 
77  	 */
78  	public void initializeContext(final Callback<Boolean> contextIntializedCallback){
79  		serverPropertiesRpcService.getContextPath(new KSAsyncCallback<String>(){
80  
81  			@Override
82  			public void onFailure(Throwable caught) {
83  				throw new RuntimeException("Fatal - Unable to initialze application context");
84  			}
85  
86  			@Override
87  			public void onSuccess(String result) {
88  				applicationContextUrl = result;
89  				securityContext = new SecurityContext();
90  				securityContext.initializeSecurityContext(contextIntializedCallback);
91  			}			
92  		});
93  	}
94  	
95  	/**
96       * Adds the messages in the list of messages to the map of the messages
97       * @param messages
98       */
99      public void addMessages(List<MessageInfo> messages) {
100 		messagesList.addAll(messages);
101 	    for (MessageInfo m : messages) {
102 	        String groupName = m.getGroupName();
103 	        Map<String, String> group = this.messages.get(groupName);
104 	        if (group == null) {
105 	            group = new HashMap<String, String>();
106 	            this.messages.put(groupName, group);
107 	        }
108 	        group.put(m.getMessageKey(), m.getValue());
109 	        flatMessages.put(m.getMessageKey(), m.getValue());
110 	    }
111 	}
112 	
113 	/**
114 	 * Get a message by a unique id
115 	 */
116 	public String getMessage(String messageId) {
117 	    return flatMessages.get(messageId);
118     }
119     
120 	/**
121 	 * Returns all the messages in the ApplicationContext
122 	 */
123 	public List<MessageInfo> getMessages() {
124 	    return messagesList;
125     }
126     
127 	
128 	/**
129 	 * Get message by the group it is in and its unique id within that group
130 	 */
131 	public String getMessage(String groupName, String messageId) {
132 			
133 	    String result = null;
134 	    
135 	    Map<String, String> group = this.messages.get(groupName);
136 	    if (group != null) {
137 	        result = group.get(messageId);
138 	    }
139 	    
140 	    return result;
141 	}
142 	
143     /**
144      * 
145      * This method looks up a UI Label in the messages cache.  
146      * First looks for a label specific to the type and state of the field.
147      * If none found try for a generalized label.
148      * Otherwise return the supplied fieldId
149      * Groups provide namespace for same label ids within different LUs
150      * 
151      * @param groupName - for example 'course' or 'program'
152      * @param type
153      * @param state
154      * @param fieldId
155      * @return
156      */
157 	 public String getUILabel(String groupName, String type, String state, String fieldId) {
158 
159         String label = getMessage(groupName, type + ":" + state + ":" + fieldId);
160         
161         if (label == null)
162             label = getMessage(groupName, fieldId);
163         
164         if (label == null)
165             label =  fieldId;
166         
167         return label;
168         
169     }
170 	 
171 	/**
172 	 * Same as getUILabel(String groupName, String type, String state, String fieldId) with no
173 	 * type and state needed
174 	 */
175 	public String getUILabel(String groupName, String fieldId) {
176 
177 	        String label = getMessage(groupName, fieldId);
178 	        
179 	        if (label == null)
180 	            label =  fieldId;
181 	        
182 	        return label;
183 	        
184 	}
185 	
186     public String getUILabel(String groupName, String type, String state, String fieldId, Metadata metadata) {
187 
188         String label = getMessage(groupName, type + ":" + state + ":" + fieldId);
189 
190         if (label == null)
191             label = getMessage(groupName, fieldId);
192 
193         if (label == null)
194             label = fieldId;
195 
196         return MessageUtils.interpolate(label, getConstraintInfo(metadata, label));
197 
198     }
199 	
200     public String getUILabel(String groupName, String fieldId, Metadata metadata) {
201 
202         String label = getMessage(groupName, fieldId);
203 
204         if (label == null)
205             label = fieldId;
206 
207         return MessageUtils.interpolate(label, getConstraintInfo(metadata, label));
208 
209     }
210 	
211     public String getUILabel(String groupName, String fieldId, Map<String, Object> parameters) {
212 
213         String label = getMessage(groupName, fieldId);
214 
215         if (label == null)
216             label = fieldId;
217 
218         return MessageUtils.interpolate(label, parameters);
219 
220     }
221 
222     /**
223      * Get the security context for the app
224      * @return SecurityContext
225      */
226     public SecurityContext getSecurityContext() {
227         return securityContext;
228     }
229 
230     public void setSecurityContext(SecurityContext securityContext) {
231         this.securityContext = securityContext;
232     }
233 	
234 	/**
235 	 * Application URL based on the serverPropertiesRPC service result
236 	 */
237 	public String getApplicationContextUrl() {
238 		return applicationContextUrl;
239 	}
240 
241 	public void setVersion(String version) {
242 		this.version = version;
243 	}
244 
245 	public String getVersion() {
246 		return version;
247 	}
248 	
249 	/**
250 	 * Adds a mapping from path to a list of field descriptors for a given namespace
251 	 * namespace defaults to _default if null
252 	 * @param path
253 	 * @param fd
254 	 */
255 	public void putCrossConstraint(String namespace, String path, HasCrossConstraints fd){
256 		if(namespace==null){
257 			namespace="_default";
258 		}
259 		
260 		HashMap<String,HashSet<HasCrossConstraints>> crossConstraintMap = crossConstraints.get(namespace);
261 		if(crossConstraintMap==null){
262 			crossConstraintMap = new HashMap<String,HashSet<HasCrossConstraints>>();
263 			crossConstraints.put(namespace, crossConstraintMap);
264 		}
265 		HashSet<HasCrossConstraints> fieldDescriptors = crossConstraintMap.get(path);
266 		if(fieldDescriptors == null){
267 			fieldDescriptors = new HashSet<HasCrossConstraints>();
268 			crossConstraintMap.put(path, fieldDescriptors);
269 		}
270 		fieldDescriptors.add(fd);
271 	}
272 
273 	
274 	
275 	public HashSet<HasCrossConstraints> getCrossConstraint(String namespace, String path){
276 		if(namespace==null){
277 			namespace="_default";
278 		}
279 		HashMap<String,HashSet<HasCrossConstraints>> crossConstraintMap = crossConstraints.get(namespace);
280 		if(crossConstraintMap!=null){
281 			return crossConstraintMap.get(path);
282 		}
283 		return null;
284 	}
285 	public void clearCrossConstraintMap(String namespace){
286 		if(namespace==null){
287 			namespace="_default";
288 		}
289 		crossConstraints.remove(namespace);
290 	}
291 	public void putPathToFieldMapping(String namespace, String path, FieldDescriptor fd){
292 		if(namespace==null){
293 			namespace="_default";
294 		}
295 		
296 		HashMap<String,FieldDescriptor> pathToField = pathToFieldMapping.get(namespace);
297 		if(pathToField==null){
298 			pathToField = new HashMap<String,FieldDescriptor>();
299 			pathToFieldMapping.put(namespace, pathToField);
300 		}
301 		pathToField.put(path, fd);
302 	}
303 
304 	public FieldDescriptor getPathToFieldMapping(String namespace, String path){
305 		if(namespace==null){
306 			namespace="_default";
307 		}
308 		
309 		HashMap<String,FieldDescriptor> pathToField = pathToFieldMapping.get(namespace);
310 		if(pathToField!=null){
311 			return pathToField.get(path);
312 		}
313 		return null;
314 	}
315 	public void clearPathToFieldMapping(String namespace){
316 		if(namespace==null){
317 			namespace="_default";
318 		}
319 		pathToFieldMapping.remove(namespace);
320 	}
321 
322 	/**
323 	 * Removes the bidirectional mapping for all paths that start with the path prefix
324 	 * This means if Field A had a dependency on Field B, and you cleared A, first all mappings with
325 	 * dependencies to A would be removed, then all mappings with dependencies to A would be removed. 
326 	 * @param namespace
327 	 * @param pathPrefix
328 	 */
329 	public void clearCrossConstraintsWithStartingPath(String namespace, String pathPrefix){
330 		if(namespace==null){
331 			namespace="_default";
332 		}
333 		//First delete any cross constraint mappings based on this field
334 		HashMap<String,HashSet<HasCrossConstraints>> crossConstraintMap = crossConstraints.get(namespace);
335 		if(crossConstraintMap!=null){
336 			Iterator<Map.Entry<String,HashSet<HasCrossConstraints>>> constraintMapIter = crossConstraintMap.entrySet().iterator();
337 			while(constraintMapIter.hasNext()){
338 				Map.Entry<String,HashSet<HasCrossConstraints>> entry = constraintMapIter.next();
339 				if(entry.getKey().startsWith(pathPrefix)){
340 					constraintMapIter.remove();
341 				}
342 			}
343 
344 			//Find all the fieldDescriptors that start with the prefix and remove the cross constraint mapping to them 
345 			HashMap<String,FieldDescriptor> pathToField = pathToFieldMapping.get(namespace);
346 			if(pathToField!=null){
347 				Iterator<Entry<String, FieldDescriptor>> pathMapIter = pathToField.entrySet().iterator();
348 				while(pathMapIter.hasNext()){
349 					Entry<String, FieldDescriptor> entry = pathMapIter.next();
350 					if(entry.getKey().startsWith(pathPrefix)){
351 						FieldDescriptor fd = entry.getValue();
352 						if(fd.getFieldWidget()!=null && fd.getFieldWidget() instanceof HasCrossConstraints && ((HasCrossConstraints)fd.getFieldWidget()).getCrossConstraints()!=null){
353 							//Loop through the constraint paths and remove any mapping to the existing field descriptor
354 							for(String path:((HasCrossConstraints)fd.getFieldWidget()).getCrossConstraints()){
355 								HashSet<HasCrossConstraints> set = crossConstraintMap.get(path);
356 								if(set!=null){
357 									set.remove(fd.getFieldWidget());
358 								}
359 							}
360 						}
361 					}
362 				}
363 			}
364 		}
365 	}
366 	
367 	public HashSet<HasCrossConstraints> getCrossConstraints(String namespace) {
368 		if(namespace==null){
369 			namespace="_default";
370 		}
371 		HashSet<HasCrossConstraints> results = new HashSet<HasCrossConstraints>();
372 		HashMap<String,HashSet<HasCrossConstraints>> crossConstraintMap = crossConstraints.get(namespace);
373 		if(crossConstraintMap!=null){
374 			for(HashSet<HasCrossConstraints> fds: crossConstraintMap.values()){
375 				results.addAll(fds);
376 			}
377 		}
378 		return results;
379 	}
380 
381 	
382 	public List<ValidationResultInfo> getValidationWarnings() {
383 		return validationWarnings;
384 	}
385 
386 	/**
387 	 * Adds warnings from the validationResults to the application context.
388 	 * 
389 	 * @param validationResults
390 	 */
391 	public void addValidationWarnings(List<ValidationResultInfo> validationResults) {
392 		if (validationResults != null){
393 			for (ValidationResultInfo vr:validationResults){
394 				if (vr.getErrorLevel() == ErrorLevel.WARN){
395 					this.validationWarnings.add(vr);
396 				}
397 			}
398 		}
399 	}
400 
401 	public void clearValidationWarnings(){
402 		validationWarnings.clear();
403 	}
404 	
405 	public String getParentPath() {
406 		return parentPath;
407 	}
408 
409 	public void setParentPath(String parentPath) {
410 		this.parentPath = parentPath;
411 	}
412 	
413     private Map<String, Object> getConstraintInfo(Metadata metadata, String label) {
414         Map<String, Object> constraintInfo = new HashMap<String, Object>();
415         
416         if (metadata != null) {
417             for (ValidationMessageKeys vKey : ValidationMessageKeys.values()) {
418                 if (!vKey.getProperty().isEmpty() && label.contains("${" + vKey.getProperty() + "}")) {
419                     switch (vKey) {
420                         case MIN_OCCURS:
421                             constraintInfo.put(vKey.getProperty(), MetadataInterrogator.getLargestMinOccurs(metadata));
422                             break;
423                         case MAX_OCCURS:
424                             constraintInfo.put(vKey.getProperty(), MetadataInterrogator.getSmallestMaxOccurs(metadata));
425                             break;
426                         case MAX_VALUE:
427                             constraintInfo.put(vKey.getProperty(), MetadataInterrogator.getSmallestMaxValue(metadata));
428                             break;
429                         case MIN_VALUE:
430                             constraintInfo.put(vKey.getProperty(), MetadataInterrogator.getLargestMinValue(metadata));
431                             break;
432                         case MAX_LENGTH:
433                             constraintInfo.put(vKey.getProperty(), MetadataInterrogator.getSmallestMaxLength(metadata));
434                             break;
435                         case MIN_LENGTH:
436                             constraintInfo.put(vKey.getProperty(), MetadataInterrogator.getLargestMinLength(metadata));
437                             break;                         
438                         case VALID_CHARS:
439                             String validChars = metadata.getConstraints().get(0).getValidChars();
440                             if (validChars.startsWith("regex:")) {
441                                 validChars = validChars.substring(6);
442                             }
443                             constraintInfo.put(vKey.getProperty(), validChars);
444                         default :
445                             break;
446                     }
447                 }
448             }
449         }
450         return constraintInfo;
451     }
452 	
453 }