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