View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the Educational Community License, Version 2.0 (the "License"); you may
3    * not use this file except in compliance with the License. You may obtain a copy of the License at
4    * http://www.osedu.org/licenses/ECL-2.0 Unless required by applicable law or agreed to in writing, software distributed
5    * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
6    * implied. See the License for the specific language governing permissions and limitations under the License.
7    */
8   
9   package org.kuali.student.r1.common.assembly.dictionary;
10  
11  import org.apache.log4j.Logger;
12  import org.kuali.student.r1.common.assembly.data.ConstraintMetadata;
13  import org.kuali.student.r1.common.assembly.data.Data;
14  import org.kuali.student.r1.common.assembly.data.Data.DataType;
15  import org.kuali.student.r1.common.assembly.data.Data.Value;
16  import org.kuali.student.r1.common.assembly.data.LookupMetadata;
17  import org.kuali.student.r1.common.assembly.data.LookupMetadata.WidgetOption;
18  import org.kuali.student.r1.common.assembly.data.LookupParamMetadata;
19  import org.kuali.student.r1.common.assembly.data.Metadata;
20  import org.kuali.student.r1.common.assembly.data.Metadata.WriteAccess;
21  import org.kuali.student.r1.common.assembly.data.UILookupConfig;
22  import org.kuali.student.r1.common.assembly.data.UILookupData;
23  import org.kuali.student.r1.common.dictionary.dto.CaseConstraint;
24  import org.kuali.student.r1.common.dictionary.dto.CommonLookupParam;
25  import org.kuali.student.r1.common.dictionary.dto.Constraint;
26  import org.kuali.student.r1.common.dictionary.dto.FieldDefinition;
27  import org.kuali.student.r1.common.dictionary.dto.ObjectStructureDefinition;
28  import org.kuali.student.r1.common.dictionary.dto.WhenConstraint;
29  import org.kuali.student.r1.common.dictionary.service.DictionaryService;
30  import org.kuali.student.r2.common.dto.DtoConstants;
31  import org.kuali.student.r2.common.dto.DtoConstants.DtoState;
32  import org.kuali.student.r2.common.infc.ValidationResult.ErrorLevel;
33  import org.kuali.student.r2.common.util.date.DateFormatters;
34  import org.springframework.beans.BeanUtils;
35  import org.springframework.context.ApplicationContext;
36  import org.springframework.context.support.ClassPathXmlApplicationContext;
37  
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.HashMap;
41  import java.util.List;
42  import java.util.Map;
43  
44  
45  /**
46   * This class provides metadata lookup for service dto objects.
47   * 
48   * @author Kuali Student Team
49   */
50  public class MetadataServiceImpl {
51      final Logger LOG = Logger.getLogger(MetadataServiceImpl.class);
52  
53      //private Map<String, DictionaryService> dictionaryServiceMap = new HashMap<String, DictionaryService>();
54      private List<DictionaryService> dictionaryServices;
55      private List<UILookupConfig> lookupObjectStructures;
56      private String uiLookupContext;
57  
58      private static class RecursionCounter {
59          public static final int MAX_DEPTH = 4;
60  
61          private Map<String, Integer> recursions = new HashMap<String, Integer>();
62  
63          public int increment(String objectName) {
64              Integer hits = recursions.get(objectName);
65  
66              if (hits == null) {
67                  hits = new Integer(1);
68              } else {
69                  hits++;
70              }
71              recursions.put(objectName, hits);
72              return hits;
73          }
74  
75          public int decrement(String objectName) {
76              Integer hits = recursions.get(objectName);
77              if (hits >= 1) {
78                  hits--;
79              }
80  
81              recursions.put(objectName, hits);
82              return hits;
83          }
84      }
85  
86  	public MetadataServiceImpl() {
87  		super();
88  	}
89  
90      /**
91       * Create a metadata service initializing it with all known dictionary services
92       * 
93       * @param dictionaryServices
94       */
95      public MetadataServiceImpl(DictionaryService... dictionaryServices) {
96      	setDictionaryServices(Arrays.asList(dictionaryServices));
97      }
98  
99      public synchronized void setDictionaryServices(List<DictionaryService> dictionaryServices) {
100         this.dictionaryServices = dictionaryServices;
101     	/*if (dictionaryServices != null) {
102     		this.dictionaryServiceMap.clear();
103             for (DictionaryService d : dictionaryServices) {
104                 List<String> objectTypes = d.getObjectTypes();
105                 for (String objectType : objectTypes) {
106                     dictionaryServiceMap.put(objectType, d);
107                 }
108             }
109     	}*/
110 	}
111 
112 
113 	/**
114      * This method gets the metadata for the given object key, type, state and nextState
115      * 
116      * @param objectKey
117      * @param type The type of the object (value can be null)
118      * @param state The state for which to retrieve object constraints (value can be null)
119      * @param nextState The state to to check requiredForNextState indicators (value can be null)
120      * @param documentTypeName The type of the document (value can be null)
121      * @return
122      */
123     public Metadata getMetadata(String objectKey, String type, String state, String nextState, String documentTypeName) {
124     	nextState = (nextState == null || nextState.length() <=0 ? DtoState.getNextStateAsString(state):nextState);
125     	state = state==null?null:state.toUpperCase();
126     	nextState = nextState==null?null:nextState.toUpperCase();
127     	//FIXME/TODO: documentTypeName is only passed here, because it is eventually used in ProgramMetadataServiceImpl's getConstraints() method
128     	return getMetadataFromDictionaryService(objectKey, type, state, nextState, null, documentTypeName);
129     }
130 
131 	/**
132      * This method gets the metadata for the given object id key, workflowNode and documentTypeName
133      * 
134      * @return
135      */
136     public Metadata getMetadataByWorkflowNode(String objectKey, String workflowNode, String documentTypeName) {
137     	//FIXME/TODO: documentTypeName is only passed here, because it is eventually used in ProgramMetadataServiceImpl's getConstraints() method
138     	return getMetadataFromDictionaryService(objectKey, null, DtoState.DRAFT.toString(), null, workflowNode, documentTypeName);
139     }
140 
141     /**
142      * This method gets the metadata for the given object key, type and state
143      * 
144      * @param objectKey
145      * @param type The type of the object (value can be null)
146      * @param state The state for which to retrieve object constraints (value can be null)
147      * @return
148      */
149     public Metadata getMetadata(String objectKey, String type, String state) {
150     	state = (state == null ? DtoState.DRAFT.toString():state);
151     	String nextState = DtoState.getNextStateAsString(state);
152     	
153     	return getMetadata(objectKey, type, state.toUpperCase(), nextState, null);
154     }
155 
156 
157     /**
158      * This method gets the metadata for the given object key and state
159      * 
160      * @param objectKey
161      * @param state
162      * @return
163      */
164     public Metadata getMetadata(String objectKey, String state) {
165     	return getMetadata(objectKey, null, state);
166     }
167 
168     /**
169      * This method gets the metadata for the given object key for state DRAFT.
170      * 
171      * @see MetadataServiceImpl#getMetadata(String, String)
172      * @param objectKey
173      * @return
174      */
175     public Metadata getMetadata(String objectKey) {
176         return getMetadata(objectKey, null, null);
177     }
178 
179     /**
180      * This invokes the appropriate dictionary service to get the object structure and then converts it to a metadata
181      * structure.
182      * 
183      * @param objectKey
184      * @param type
185      * @param state
186      * @param documentTypeName
187      * @return
188      */
189     protected Metadata getMetadataFromDictionaryService(String objectKey, String type, String state, String nextState, String workflowNode, String documentTypeName) {
190         
191         Metadata metadata = new Metadata();
192 
193         ObjectStructureDefinition objectStructure = getObjectStructure(objectKey);
194 		//FIXME/TODO: documentTypeName is only passed here, because it is eventually used in ProgramMetadataServiceImpl's getConstraints() method
195         metadata.setProperties(getProperties(objectStructure, type, state, nextState, workflowNode, new RecursionCounter(), documentTypeName));
196 
197         metadata.setWriteAccess(WriteAccess.ALWAYS);
198         metadata.setDataType(DataType.DATA);
199         addLookupstoMetadata(objectKey, metadata, type);
200         return metadata;
201     }
202 
203     /**
204      * This method is used to convert a list of dictionary fields into metadata properties
205      *
206      * @param objectStructure
207      * @param type
208      * @param state
209      * @param nextState
210      * @param workflowNode
211      * @param counter
212      * @param documentTypeName
213      * @return
214      */
215     private Map<String, Metadata> getProperties(ObjectStructureDefinition objectStructure, String type, String state, String nextState, String workflowNode, RecursionCounter counter, String documentTypeName) {
216         String objectName = objectStructure.getName();
217         int hits = counter.increment(objectName);
218 
219         Map<String, Metadata> properties = null;
220 
221         if (hits < RecursionCounter.MAX_DEPTH) {
222             properties = new HashMap<String, Metadata>();
223 
224             List<FieldDefinition> attributes = objectStructure.getAttributes();
225             for (FieldDefinition fd : attributes) {
226 
227                 Metadata metadata = new Metadata();
228 
229                 // Set constraints, authz flags, default value
230                 metadata.setWriteAccess(WriteAccess.ALWAYS);
231                 metadata.setDataType(convertDictionaryDataType(fd.getDataType()));
232                 //FIXME/TODO: documentTypeName is only passed here, because it is eventually used in ProgramMetadataServiceImpl's getConstraints() method
233                 metadata.setConstraints(getConstraints(fd, type, state, nextState, workflowNode, documentTypeName));
234                 metadata.setCanEdit(!fd.isReadOnly());
235                 metadata.setCanUnmask(!fd.isMask());
236                 metadata.setCanView(!fd.isHide());
237                 metadata.setDynamic(fd.isDynamic());
238                 metadata.setLabelKey(fd.getLabelKey());
239                 metadata.setDefaultValue(convertDefaultValue(metadata.getDataType(), fd.getDefaultValue()));
240                 metadata.setDefaultValuePath(fd.getDefaultValuePath());
241                 
242 	           	if (fd.isPartialMask()){
243 	           		metadata.setPartialMaskFormatter(fd.getPartialMaskFormatter());
244 	           	}
245 	           	
246 	           	if (fd.isMask()){
247 	           		metadata.setMaskFormatter(fd.getMaskFormatter());
248 	           	}
249 
250                 // Get properties for nested object structure
251                 Map<String, Metadata> nestedProperties = null;
252                 if (fd.getDataType() == org.kuali.student.r1.common.dictionary.dto.DataType.COMPLEX && fd.getDataObjectStructure() != null) {
253                     nestedProperties = getProperties(fd.getDataObjectStructure(), type, state, nextState, workflowNode, counter, documentTypeName);
254                 }
255 
256                 // For repeating field, create a LIST with wildcard in metadata structure
257                 if (isRepeating(fd)) {
258                     Metadata repeatingMetadata = new Metadata();
259                     metadata.setDataType(DataType.LIST);
260 
261                     repeatingMetadata.setWriteAccess(WriteAccess.ALWAYS);
262                     repeatingMetadata.setOnChangeRefreshMetadata(false);
263                     repeatingMetadata.setDataType(convertDictionaryDataType(fd.getDataType()));
264 
265                     if (nestedProperties != null) {
266                         repeatingMetadata.setProperties(nestedProperties);
267                     }
268 
269                     Map<String, Metadata> repeatingProperty = new HashMap<String, Metadata>();
270                     repeatingProperty.put("*", repeatingMetadata);
271                     metadata.setProperties(repeatingProperty);
272                 } else if (nestedProperties != null) {
273                     metadata.setProperties(nestedProperties);
274                 }
275 
276                 properties.put(fd.getName(), metadata);
277 
278             }
279         }
280 
281         counter.decrement(objectName);
282         return properties;
283     }
284 
285     /**
286      * This method determines if a field is repeating
287      * 
288      * @param fd
289      * @return
290      */
291     protected boolean isRepeating(FieldDefinition fd) {
292         boolean isRepeating = false;
293         try {
294             int maxOccurs = Integer.parseInt(fd.getMaxOccurs());
295             isRepeating = maxOccurs > 1;
296         } catch (NumberFormatException nfe) {
297             isRepeating = FieldDefinition.UNBOUNDED.equals(fd.getMaxOccurs());
298         }
299 
300         return isRepeating;
301     }
302 
303     /**
304      * This method gets the object structure for given objectKey from a dictionaryService
305      * 
306      * @param objectKey
307      * @return
308      */
309     protected ObjectStructureDefinition getObjectStructure(String objectKey) {
310         //DictionaryService dictionaryService = dictionaryServiceMap.get(objectKey);
311         
312         DictionaryService dictionaryService = null;
313         for (DictionaryService service : this.dictionaryServices){
314             List<String> objectTypes = service.getObjectTypes();
315             for (String objectType : objectTypes) {
316                 if (objectType.equals(objectKey)){
317                     dictionaryService = service;
318                 }
319             }
320         }
321 
322         if (dictionaryService == null) {
323             throw new RuntimeException("Dictionary service not provided for objectKey=[" + objectKey + "].");
324         }
325 
326         return dictionaryService.getObjectStructure(objectKey);
327     }
328 
329 	//FIXME/TODO: documentTypeName is only passed here, because it is used(overridden) in ProgramMetadataServiceImpl's getConstraints() method
330     protected List<ConstraintMetadata> getConstraints(FieldDefinition fd, String type, String state, String nextState, String workflowNode, String documentTypeName) {
331         List<ConstraintMetadata> constraints = new ArrayList<ConstraintMetadata>();
332 
333         ConstraintMetadata constraintMetadata = new ConstraintMetadata();
334 
335         updateConstraintMetadata(constraintMetadata, (Constraint) fd, type, state, nextState, workflowNode);
336         constraints.add(constraintMetadata);
337 
338         return constraints;
339     }
340 
341     /**
342      * This updates the constraintMetadata with defintions from the dictionary constraint field.
343      * 
344      * @param constraintMetadata
345      * @param constraint
346      */
347     protected void updateConstraintMetadata(ConstraintMetadata constraintMetadata, Constraint constraint, String type, String state, String nextState, String workflowNode) {
348         // For now ignoring the serverSide flag and making determination of which constraints
349         // should be passed up to the UI via metadata.
350 
351         // Min Length
352         if (constraint.getMinLength() != null) {
353             constraintMetadata.setMinLength(constraint.getMinLength());
354         }
355 
356         // Max Length
357         try {
358             if (constraint.getMaxLength() != null) {
359                 constraintMetadata.setMaxLength(Integer.parseInt(constraint.getMaxLength()));
360             }
361             // Do we need to add another constraint and label it required if minOccurs = 1
362         } catch (NumberFormatException nfe) {
363             // Ignoring an unbounded length, cannot be handled in metadata structure, maybe change Metadata to string or set
364             // to -1
365             constraintMetadata.setMaxLength(9999);
366         }
367 
368         // Min Occurs
369         if (constraint.getMinOccurs() != null) {
370             constraintMetadata.setMinOccurs(constraint.getMinOccurs());
371         }
372 
373         // Max Occurs
374         String maxOccurs = constraint.getMaxOccurs();
375         if (maxOccurs != null) {
376             try {
377                 constraintMetadata.setMaxOccurs(Integer.parseInt(maxOccurs));
378                 if (!FieldDefinition.SINGLE.equals(maxOccurs)) {
379                     constraintMetadata.setId("repeating");
380                 }
381             } catch (NumberFormatException nfe) {
382                 // Setting unbounded to a value of 9999, since unbounded not handled by metadata
383                 if (FieldDefinition.UNBOUNDED.equals(maxOccurs)) {
384                     constraintMetadata.setId("repeating");
385                     constraintMetadata.setMaxOccurs(9999);
386                 }
387             }
388         }
389 
390         // Min Value
391         if (constraint.getExclusiveMin() != null) {
392             constraintMetadata.setMinValue(constraint.getExclusiveMin());
393         }
394 
395         // Max Value
396         if (constraint.getInclusiveMax() != null) {
397             constraintMetadata.setMaxValue(constraint.getInclusiveMax());
398         }
399 
400         if (constraint.getValidChars() != null) {
401             constraintMetadata.setValidChars(constraint.getValidChars().getValue());
402             constraintMetadata.setValidCharsMessageId(constraint.getValidChars().getLabelKey());
403         }
404 
405         // Case constraints
406         if (constraint.getCaseConstraint() != null) {
407             processCaseConstraint(constraintMetadata, constraint.getCaseConstraint(), type, state, nextState, workflowNode);
408         }
409     }
410 
411     /**
412      * Currently this only handles requiredness indicators for case constraints with the following field paths:
413      * 
414      *  type, state, and proposal/workflowNode
415      */
416     protected void processCaseConstraint(ConstraintMetadata constraintMetadata, CaseConstraint caseConstraint, String type, String state, String nextState, String workflowNode) {
417         String fieldPath = caseConstraint.getFieldPath();
418         fieldPath = (fieldPath != null ? fieldPath.toUpperCase() : fieldPath);
419         
420         if (workflowNode != null && fieldPath != null && fieldPath.startsWith("PROPOSAL/WORKFLOWNODE")){
421         	processRequiredByNodeCaseConstraint(constraintMetadata, caseConstraint, workflowNode);
422           //Both 'stateKey' and 'state' needs to be checked until R1 code is phased out
423         } else if ("STATEKEY".equals(fieldPath) || "STATE".equals(fieldPath)) {
424         	processStateCaseConstraint(constraintMetadata, caseConstraint, type, state, nextState, workflowNode);
425           //Both 'typeKey' and 'type' needs to be checked until R1 code is phased out
426         } else if ("TYPEKEY".equals(fieldPath) || "TYPE".equals(fieldPath)) {
427         	processTypeCaseConstraint(constraintMetadata, caseConstraint, type, state, nextState, workflowNode);
428         }
429     }
430         
431     /**
432      * Modifies the constraintMetadata to add required to save or required to approve constraints based on the 
433      * workflow route node the proposal is currently in.
434      * 
435      * @param constraintMetadata The fields constraintMetadata to be modified
436      * @param caseConstraint The caseConstraint defined in dictionary for field
437      * @param workflowNode The current node in workflow process
438      */
439     private void processRequiredByNodeCaseConstraint(ConstraintMetadata constraintMetadata, CaseConstraint caseConstraint,  String workflowNode) {
440         List<WhenConstraint> whenConstraints = caseConstraint.getWhenConstraint();
441         
442     	if ("EQUALS".equals(caseConstraint.getOperator()) && whenConstraints != null) {
443             for (WhenConstraint whenConstraint : whenConstraints) {
444                 List<Object> values = whenConstraint.getValues();
445                 Constraint constraint = whenConstraint.getConstraint();
446 
447                 if (constraint.getErrorLevel() == ErrorLevel.ERROR && constraint.getMinOccurs() != null && constraint.getMinOccurs() > 0){
448                     //This is a required field, so need to determine if it is required to save or required to approve based on the
449                 	//workflowNode parameter. The order of workflow nodes defined in the case constraint on this field is important in 
450                 	//determining if required to approve or required to save. If the workflowNode parameter equals is the first  
451                 	//node defined in the constraint, then it's required to approve, otherwise it's required to save.
452                		
453                 	if (isWorkflowNodeFirstConstraintValue(workflowNode, values)) {
454                 		//Field is required to approve. Indicated this by setting the required for next state flag in metadata.
455                 		//If node is PreRoute, then the next state will be set to "SUBMIT" to indicate submit action, otherwise
456                 		//will be set to "APPROVED" to indicate approval action for node transition.
457                			constraintMetadata.setRequiredForNextState(true);
458                			if (DtoConstants.WORKFLOW_NODE_PRE_ROUTE.equals(workflowNode)){
459                				constraintMetadata.setNextState(DtoState.SUBMITTED.toString());
460                			} else {
461                				constraintMetadata.setNextState(DtoState.APPROVED.toString());
462                			}
463                			constraintMetadata.setMinOccurs(0);
464                     } else if (values.contains(workflowNode)){
465                     	//Field is required only for save
466                			constraintMetadata.setRequiredForNextState(false);
467                			constraintMetadata.setNextState(null);
468                			constraintMetadata.setMinOccurs(1);
469                     }
470                 }
471             }
472         }
473     }
474     
475     /** 
476      * @param values
477      * @param workflowNode
478      * @return true if workflowNode is first item in values, otherwise returns false
479      */
480     private boolean isWorkflowNodeFirstConstraintValue(String workflowNode, List<Object> values){
481     	if (values != null && !values.isEmpty()){
482     		return values.get(0).equals(workflowNode);
483     	} else {
484     		return false;
485     	}
486     }
487 
488 	/**
489      * Processes a case constraint with field path of state. 
490      */
491     private void processStateCaseConstraint(ConstraintMetadata constraintMetadata,	CaseConstraint caseConstraint, String type, String state, String nextState, String workflowNode) {
492         List<WhenConstraint> whenConstraints = caseConstraint.getWhenConstraint();
493         
494         if ("EQUALS".equals(caseConstraint.getOperator()) && whenConstraints != null) {
495             for (WhenConstraint whenConstraint : whenConstraints) {
496                 List<Object> values = whenConstraint.getValues();
497                 if (values != null) {
498                     Constraint constraint = whenConstraint.getConstraint();
499 
500                     if (constraint.getErrorLevel() == ErrorLevel.ERROR){
501                     	//NOTE: if the constraint has a nested constraint with fieldPath="lookup:proposal...", 
502                     	//the required, requiredForNextState, and nextState values will be reset based on workflow node	           
503                     	
504                     	// Set the required for next state flag. 
505                         if (values.contains(nextState)) {
506                             if (constraint.getMinOccurs() != null && constraint.getMinOccurs() > 0) {
507                                 constraintMetadata.setRequiredForNextState(true);
508                                 constraintMetadata.setNextState(nextState);
509                             }
510                         }
511 
512                         // Update constraints based on state constraints
513                         if (values.contains(state)) {
514                             updateConstraintMetadata(constraintMetadata, constraint, type, state, nextState, workflowNode);
515                         }
516                     }
517                 }
518             }
519         }		
520 	}
521     
522     /**
523      * Process a case constraint with fieldPath of type 
524      */
525     private void processTypeCaseConstraint(ConstraintMetadata constraintMetadata, CaseConstraint caseConstraint, String type, String state,	String nextState, String workflowNode) {
526         List<WhenConstraint> whenConstraints = caseConstraint.getWhenConstraint();
527     	
528         if ("EQUALS".equals(caseConstraint.getOperator()) && whenConstraints != null) {
529             for (WhenConstraint whenConstraint : whenConstraints) {
530                 List<Object> values = whenConstraint.getValues();
531                 if (values != null && values.contains(type)) {
532                     Constraint constraint = whenConstraint.getConstraint();
533                     updateConstraintMetadata(constraintMetadata, constraint, type, state, nextState, workflowNode);
534                 }
535             }
536         }		
537 	}
538     
539 
540 	/**
541      * Convert Object value to respective DataType. Method return null for object Value.
542      * 
543      * @param dataType
544      * @param value
545      * @return
546      */
547     protected Value convertDefaultValue(DataType dataType, Object value) {
548         Value v = null;
549         if (value instanceof String) {
550             String s = (String) value;
551             switch (dataType) {
552                 case STRING:
553                     v = new Data.StringValue(s);
554                     break;
555                 case BOOLEAN:
556                     v = new Data.BooleanValue(Boolean.valueOf(s));
557                     break;
558                 case FLOAT:
559                     v = new Data.FloatValue(Float.valueOf(s));
560                     break;
561                 case DATE:
562                     try {
563                         v = new Data.DateValue(DateFormatters.DEFAULT_DATE_FORMATTER.parse(s));
564                     } catch (IllegalArgumentException e) {
565                         LOG.error("Unable to get default date value from metadata definition");
566                     }
567                     break;
568                 case LONG:
569                     if (!s.isEmpty()) {
570                         v = new Data.LongValue(Long.valueOf(s));
571                     }
572                     break;
573                 case DOUBLE:
574                     v = new Data.DoubleValue(Double.valueOf(s));
575                     break;
576                 case INTEGER:
577                     v = new Data.IntegerValue(Integer.valueOf(s));
578                     break;
579             }
580         } else {
581             v = convertDefaultValue(value);
582         }
583 
584         return v;
585     }
586 
587     protected Value convertDefaultValue(Object value) {
588         Value v = null;
589 
590         if (value instanceof String) {
591             v = new Data.StringValue((String) value);
592         } else if (value instanceof Boolean) {
593             v = new Data.BooleanValue((Boolean) value);
594         } else if (value instanceof Integer) {
595             v = new Data.IntegerValue((Integer) value);
596         } else if (value instanceof Double) {
597             v = new Data.DoubleValue((Double) value);
598         } else if (value instanceof Long) {
599             v = new Data.LongValue((Long) value);
600         } else if (value instanceof Short) {
601             v = new Data.ShortValue((Short) value);
602         } else if (value instanceof Float) {
603             v = new Data.FloatValue((Float) value);
604         }
605 
606         return v;
607     }
608 
609     protected DataType convertDictionaryDataType(org.kuali.student.r1.common.dictionary.dto.DataType dataType) {
610         switch (dataType) {
611             case STRING:
612                 return DataType.STRING;
613             case BOOLEAN:
614                 return DataType.BOOLEAN;
615             case INTEGER:
616                 return DataType.INTEGER;
617             case FLOAT:
618                 return DataType.FLOAT;
619             case COMPLEX:
620                 return DataType.DATA;
621             case DATE:
622                 return DataType.DATE;
623             case DOUBLE:
624                 return DataType.DOUBLE;
625             case LONG:
626                 return DataType.LONG;
627         }
628 
629         return null;
630     }
631 
632     public void setUiLookupContext(String uiLookupContext) {
633         this.uiLookupContext = uiLookupContext;
634         init();
635 
636     }
637 
638     @SuppressWarnings("unchecked")
639     private void init() {
640         ApplicationContext ac = new ClassPathXmlApplicationContext(uiLookupContext);
641 
642         Map<String, UILookupConfig> beansOfType = (Map<String, UILookupConfig>) ac.getBeansOfType(UILookupConfig.class);
643         lookupObjectStructures = new ArrayList<UILookupConfig>();
644         for (UILookupConfig objStr : beansOfType.values()) {
645             lookupObjectStructures.add(objStr);
646         }
647         System.out.println("UILookup loaded");
648     }
649 
650     private String calcSimpleName(String objectKey) {
651         int lastDot = objectKey.lastIndexOf(".");
652         if (lastDot == -1) {
653             return objectKey;
654         }
655         return objectKey.substring(lastDot + 1);
656 
657     }
658 
659     private boolean matchesObjectKey(String objectKey, String path) {
660         String simpleName = calcSimpleName(objectKey);
661         if (path.toLowerCase().startsWith(simpleName.toLowerCase())) {
662             // System.out.println ("matchesObjectKey: is TRUE for " + objectKey + " and " + path);
663             return true;
664         }
665         // System.out.println ("matchesObjectKey: is FALSE for " + objectKey + " and " + path);
666         return false;
667     }
668 
669     private boolean matchesType(String paramType, String lookupType) {
670         // both null
671         if (paramType == null && lookupType == null) {
672             return true;
673         }
674         // not asking for type specific but the lookup defnition is type specific then
675         // no match
676         if (paramType == null && lookupType != null) {
677             return false;
678         }
679         // if looking for type specific but the lookup is not specific then
680         // take as default
681         // If configuration has both a null type (i.e. default) AND has a type
682         // specific one the type specific one has to be entered into the configuration
683         // file first so it is found first
684         if (paramType != null && lookupType == null) {
685             return true;
686         }
687         if (paramType.equalsIgnoreCase(lookupType)) {
688             // System.out.println ("matchesType: is TRUE for " + paramType + " and " + lookupType);
689             return true;
690         }
691         // System.out.println ("matchesType: is FALSE for " + paramType + " and " + lookupType);
692         return false;
693     }
694 
695     private void addLookupstoMetadata(String objectKey, Metadata metadata, String type) {
696         if (lookupObjectStructures != null) {
697             for (UILookupConfig lookup : lookupObjectStructures) {
698                 if (!matchesObjectKey(objectKey, lookup.getPath())) {
699                     continue;
700                 }
701                 if (!matchesType(type, lookup.getType())) {
702                     continue;
703                 }
704                 // TODO: figure out why path=courseInfo.creditOptions.type matches any structure that has a type on it so
705                 // that lookup gets returned for all types
706                 Map<String, Metadata> parsedMetadataMap = metadata.getProperties();
707                 Metadata parsedMetadata = null;
708                 String parsedMetadataKey = "";
709                 String lookupFieldPath = lookup.getPath();
710                 String[] lookupPathTokens = getPathTokens(lookupFieldPath);
711                 for (int i = 1; i < lookupPathTokens.length; i++) {
712                     if (parsedMetadataMap == null) {
713                         break;
714                     }
715                     if (i == lookupPathTokens.length - 1) {
716                         // get the metadata on the last path key token
717                         parsedMetadata = parsedMetadataMap.get(lookupPathTokens[i]);
718                         parsedMetadataKey = parsedMetadataKey + "." + lookupPathTokens[i];
719                     }
720                     if (parsedMetadataMap.get(lookupPathTokens[i]) != null) {
721                         parsedMetadataMap = parsedMetadataMap.get(lookupPathTokens[i]).getProperties();
722                     } else if (parsedMetadataMap.get("*") != null) {
723                         // Lookup wildcard in case of unbounded elements in metadata.
724                         parsedMetadataMap = parsedMetadataMap.get("*").getProperties();
725                         i--;
726                     }
727 
728                 }
729                 if (parsedMetadata != null) {
730                     // System.out.println ("addLookupstoMetadata:" + parsedMetadataKey + " was found as a match for " +
731                     // lookup.getPath ());
732                     UILookupData initialLookup = lookup.getInitialLookup();
733                     if (initialLookup != null) {
734                         mapLookupDatatoMeta(initialLookup);
735                         parsedMetadata.setInitialLookup(mapLookupDatatoMeta(lookup.getInitialLookup()));
736                     }
737                     List<LookupMetadata> additionalLookupMetadata = null;
738                     if (lookup.getAdditionalLookups() != null) {
739                         additionalLookupMetadata = new ArrayList<LookupMetadata>();
740                         for (UILookupData additionallookup : lookup.getAdditionalLookups()) {
741                             additionalLookupMetadata.add(mapLookupDatatoMeta(additionallookup));
742                         }
743                         parsedMetadata.setAdditionalLookups(additionalLookupMetadata);
744                     }
745                 }
746             }
747         }
748     }
749 
750     private LookupMetadata mapLookupDatatoMeta(UILookupData lookupData) {
751         LookupMetadata lookupMetadata = new LookupMetadata();
752         List<LookupParamMetadata> paramsMetadata;
753         BeanUtils.copyProperties(lookupData, lookupMetadata, new String[]{"widget", "usage", "widgetOptions", "params"});
754         if (lookupData.getWidget() != null) {
755             lookupMetadata.setWidget(org.kuali.student.r1.common.assembly.data.LookupMetadata.Widget.valueOf(lookupData.getWidget().toString()));
756         }
757         if (lookupData.getUsage() != null) {
758             lookupMetadata.setUsage(org.kuali.student.r1.common.assembly.data.LookupMetadata.Usage.valueOf(lookupData.getUsage().toString()));
759         }
760         if (lookupData.getWidgetOptions () != null) {
761          lookupMetadata.setWidgetOptions (new HashMap<WidgetOption, String> ());
762          for (UILookupData.WidgetOption wo: lookupData.getWidgetOptions ().keySet ()) {
763           String value = lookupData.getWidgetOptions ().get (wo);
764           LookupMetadata.WidgetOption key = LookupMetadata.WidgetOption.valueOf(wo.toString());
765           lookupMetadata.getWidgetOptions ().put (key, value);
766          }
767         }
768         if (lookupData.getParams() != null) {
769             paramsMetadata = new ArrayList<LookupParamMetadata>();
770             for (CommonLookupParam param : lookupData.getParams()) {
771                 paramsMetadata.add(mapLookupParamMetadata(param));
772             }
773             lookupMetadata.setParams(paramsMetadata);
774         }
775         // WidgetOptions is not used as of now. So not setting it into metadata.
776         return lookupMetadata;
777     }
778 
779     private LookupParamMetadata mapLookupParamMetadata(CommonLookupParam param) {
780         LookupParamMetadata paramMetadata = new LookupParamMetadata();
781         BeanUtils.copyProperties(param, paramMetadata, new String[]{"childLookup", "dataType", "writeAccess", "usage", "widget"});
782         if (param.getChildLookup() != null) {
783             paramMetadata.setChildLookup(mapLookupDatatoMeta((UILookupData) param.getChildLookup()));
784         }
785         if (param.getDataType() != null) {
786             paramMetadata.setDataType(org.kuali.student.r1.common.assembly.data.Data.DataType.valueOf(param.getDataType().toString()));
787         }
788         if (param.getWriteAccess() != null) {
789             paramMetadata.setWriteAccess(org.kuali.student.r1.common.assembly.data.Metadata.WriteAccess.valueOf(param.getWriteAccess().toString()));
790         }
791         if (param.getUsage() != null) {
792             paramMetadata.setUsage(org.kuali.student.r1.common.assembly.data.LookupMetadata.Usage.valueOf(param.getUsage().toString()));
793         }
794         if (param.getWidget() != null) {
795             paramMetadata.setWidget(org.kuali.student.r1.common.assembly.data.LookupParamMetadata.Widget.valueOf(param.getWidget().toString()));
796         }
797 
798         return paramMetadata;
799     }
800 
801     private static String[] getPathTokens(String fieldPath) {
802         return (fieldPath != null && fieldPath.contains(".") ? fieldPath.split("\\.") : new String[]{fieldPath});
803     }
804 }