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