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