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