001    /**
002     * Copyright 2010 The Kuali Foundation Licensed under the Educational Community License, Version 2.0 (the "License"); you may
003     * not use this file except in compliance with the License. You may obtain a copy of the License at
004     * http://www.osedu.org/licenses/ECL-2.0 Unless required by applicable law or agreed to in writing, software distributed
005     * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
006     * implied. See the License for the specific language governing permissions and limitations under the License.
007     */
008    
009    package org.kuali.student.common.assembly.dictionary;
010    
011    import java.text.DateFormat;
012    import java.text.ParseException;
013    import java.text.SimpleDateFormat;
014    import java.util.ArrayList;
015    import java.util.HashMap;
016    import java.util.List;
017    import java.util.Map;
018    
019    import org.apache.log4j.Logger;
020    import org.kuali.student.common.assembly.data.ConstraintMetadata;
021    import org.kuali.student.common.assembly.data.Data;
022    import org.kuali.student.common.assembly.data.LookupMetadata;
023    import org.kuali.student.common.assembly.data.LookupParamMetadata;
024    import org.kuali.student.common.assembly.data.Metadata;
025    import org.kuali.student.common.assembly.data.UILookupConfig;
026    import org.kuali.student.common.assembly.data.UILookupData;
027    import org.kuali.student.common.assembly.data.Data.DataType;
028    import org.kuali.student.common.assembly.data.Data.Value;
029    import org.kuali.student.common.assembly.data.Metadata.WriteAccess;
030    import org.kuali.student.common.dictionary.dto.CaseConstraint;
031    import org.kuali.student.common.dictionary.dto.CommonLookupParam;
032    import org.kuali.student.common.dictionary.dto.Constraint;
033    import org.kuali.student.common.dictionary.dto.FieldDefinition;
034    import org.kuali.student.common.dictionary.dto.ObjectStructureDefinition;
035    import org.kuali.student.common.dictionary.dto.WhenConstraint;
036    import org.kuali.student.common.dictionary.service.DictionaryService;
037    import org.kuali.student.common.dto.DtoConstants.DtoState;
038    import org.springframework.beans.BeanUtils;
039    import org.springframework.context.ApplicationContext;
040    import org.springframework.context.support.ClassPathXmlApplicationContext;
041    
042    /**
043     * This class provides metadata lookup for service dto objects.
044     * 
045     * @author Kuali Student Team
046     */
047    public class MetadataServiceImpl {
048        final Logger LOG = Logger.getLogger(MetadataServiceImpl.class);
049    
050        private Map<String, DictionaryService> dictionaryServiceMap;
051        private List<UILookupConfig> lookupObjectStructures;
052        private String uiLookupContext;
053    
054        private static class RecursionCounter {
055            public static final int MAX_DEPTH = 4;
056    
057            private Map<String, Integer> recursions = new HashMap<String, Integer>();
058    
059            public int increment(String objectName) {
060                Integer hits = recursions.get(objectName);
061    
062                if (hits == null) {
063                    hits = new Integer(1);
064                } else {
065                    hits++;
066                }
067                recursions.put(objectName, hits);
068                return hits;
069            }
070    
071            public int decrement(String objectName) {
072                Integer hits = recursions.get(objectName);
073                if (hits >= 1) {
074                    hits--;
075                }
076    
077                recursions.put(objectName, hits);
078                return hits;
079            }
080        }
081    
082        /**
083         * Create a metadata service initializing it with all known dictionary services
084         * 
085         * @param dictionaryServices
086         */
087        public MetadataServiceImpl(DictionaryService... dictionaryServices) {
088            if (dictionaryServices != null) {
089                this.dictionaryServiceMap = new HashMap<String, DictionaryService>();
090                for (DictionaryService d : dictionaryServices) {
091                    List<String> objectTypes = d.getObjectTypes();
092                    for (String objectType : objectTypes) {
093                        dictionaryServiceMap.put(objectType, d);
094                    }
095                }
096            }
097        }
098    
099        /**
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    }