Coverage Report - org.kuali.student.core.assembly.dictionary.old.MetadataServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
MetadataServiceImpl
76%
139/182
68%
67/98
4.556
MetadataServiceImpl$1
100%
1/1
N/A
4.556
MetadataServiceImpl$RecursionCounter
100%
13/13
75%
3/4
4.556
 
 1  
 /**
 2  
  * Copyright 2010 The Kuali Foundation Licensed under the
 3  
  * Educational Community License, Version 2.0 (the "License"); you may
 4  
  * not use this file except in compliance with the License. You may
 5  
  * obtain a copy of the License at
 6  
  *
 7  
  * http://www.osedu.org/licenses/ECL-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing,
 10  
  * software distributed under the License is distributed on an "AS IS"
 11  
  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 12  
  * or implied. See the License for the specific language governing
 13  
  * permissions and limitations under the License.
 14  
  */
 15  
 
 16  
 package org.kuali.student.core.assembly.dictionary.old;
 17  
 
 18  
 import java.text.DateFormat;
 19  
 import java.text.ParseException;
 20  
 import java.text.SimpleDateFormat;
 21  
 import java.util.ArrayList;
 22  
 import java.util.HashMap;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 
 26  
 import org.apache.log4j.Logger;
 27  
 import org.kuali.student.core.assembly.data.ConstraintMetadata;
 28  
 import org.kuali.student.core.assembly.data.Data;
 29  
 import org.kuali.student.core.assembly.data.Metadata;
 30  
 import org.kuali.student.core.assembly.data.Data.DataType;
 31  
 import org.kuali.student.core.assembly.data.Data.Value;
 32  
 import org.kuali.student.core.assembly.data.Metadata.WriteAccess;
 33  
 import org.kuali.student.core.dictionary.old.dto.ConstraintDescriptor;
 34  
 import org.kuali.student.core.dictionary.old.dto.ConstraintSelector;
 35  
 import org.kuali.student.core.dictionary.old.dto.Field;
 36  
 import org.kuali.student.core.dictionary.old.dto.FieldDescriptor;
 37  
 import org.kuali.student.core.dictionary.old.dto.ObjectStructure;
 38  
 import org.kuali.student.core.dictionary.old.dto.State;
 39  
 import org.kuali.student.core.dictionary.old.dto.Type;
 40  
 import org.kuali.student.core.dictionary.service.old.DictionaryService;
 41  
 import org.springframework.context.ConfigurableApplicationContext;
 42  
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 43  
 import org.springframework.util.StringUtils;
 44  
 
 45  
 /**
 46  
  * This class provides metadata lookup services for orchestration objects.
 47  
  * 
 48  
  *  TODO: 
 49  
  *      1) Handle type state configuration & better caching
 50  
  *      2) Differentiate b/w metadata structure required for client vs. assemblers
 51  
  *      3) Namespace collision b/w service dictionaries and orchestration dictionary?      
 52  
  * 
 53  
  * @author Kuali Student Team
 54  
  *
 55  
  */
 56  
 @Deprecated
 57  
 public class MetadataServiceImpl {
 58  2
     final Logger LOG = Logger.getLogger(MetadataServiceImpl.class);
 59  
     
 60  2
     private Map<String, Object> metadataRepository = null;
 61  
     
 62  
     private Map<String, DictionaryService> dictionaryServiceMap;
 63  
     
 64  36
     private static class RecursionCounter{
 65  
         public static final int MAX_DEPTH = 4;
 66  
         
 67  18
         private Map<String, Integer> recursions = new HashMap<String, Integer>();
 68  
         
 69  
         public int increment(String objectName){
 70  57
             Integer hits = recursions.get(objectName);
 71  
             
 72  57
             if (hits == null){
 73  35
                 hits = new Integer(1);
 74  
             } else {
 75  22
                 hits++;
 76  
             }
 77  57
             recursions.put(objectName, hits);
 78  57
             return hits;
 79  
         }
 80  
         
 81  
         public int decrement(String objectName){
 82  57
             Integer hits = recursions.get(objectName);
 83  57
              if (hits >= 1){
 84  57
                  hits--;
 85  
              }
 86  
 
 87  57
              recursions.put(objectName, hits);
 88  57
              return hits;
 89  
         }
 90  
     }
 91  
     
 92  
     /**
 93  
      * Create a Metadata service initialized using a given classpath metadata context file
 94  
      * 
 95  
      * @param metadataContext the classpath metadata context file
 96  
      */
 97  1
     public MetadataServiceImpl(String metadataContext){
 98  1
         init(metadataContext, (DictionaryService[])null);
 99  1
     }
 100  
     
 101  1
     public MetadataServiceImpl(DictionaryService...dictionaryServices){        
 102  1
         init(null, dictionaryServices);
 103  1
     }
 104  
 
 105  0
     public MetadataServiceImpl(String metadataContext, DictionaryService...dictionaryServices){        
 106  0
         init(metadataContext, dictionaryServices);
 107  0
     }
 108  
     
 109  
     @SuppressWarnings("unchecked")
 110  
     private void init(String metadataContext, DictionaryService...dictionaryServices){
 111  2
         if (metadataContext != null){
 112  1
                 String[] locations = StringUtils.tokenizeToStringArray(metadataContext, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
 113  1
                     ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(locations);
 114  
                     
 115  1
             Map<String, DataObjectStructure> beansOfType = (Map<String, DataObjectStructure>) context.getBeansOfType(DataObjectStructure.class);
 116  1
             metadataRepository = new HashMap<String, Object>();
 117  1
             for (DataObjectStructure dataObjStr : beansOfType.values()){
 118  18
                 metadataRepository.put(dataObjStr.getName(), getProperties(dataObjStr, new RecursionCounter()));
 119  
             }
 120  
         }
 121  
         
 122  2
         if (dictionaryServices != null){
 123  1
             this.dictionaryServiceMap = new HashMap<String, DictionaryService>();
 124  2
             for (DictionaryService d:dictionaryServices){
 125  1
                 List<String> objectTypes = d.getObjectTypes();
 126  1
                 for(String objectType:objectTypes){
 127  5
                     dictionaryServiceMap.put(objectType, d);
 128  
                 }            
 129  
             }            
 130  
         }
 131  
         
 132  2
     }
 133  
     
 134  
     /** 
 135  
      * This method gets the metadata for the given object key
 136  
      * 
 137  
      * @param objectKey
 138  
      * @param type
 139  
      * @param state
 140  
      * @return
 141  
      */
 142  
     @SuppressWarnings("unchecked")
 143  
     public Metadata getMetadata(String objectKey, String type, String state){
 144  3
         if (metadataRepository == null || metadataRepository.get(objectKey) == null){
 145  1
             return getMetadataFromDictionaryService(objectKey, type, state);
 146  
         } else {        
 147  2
             Metadata metadata = new Metadata ();
 148  2
             metadata.setWriteAccess(WriteAccess.ALWAYS);
 149  2
             metadata.setOnChangeRefreshMetadata(false);
 150  2
             metadata.setDataType(DataType.DATA);
 151  
 
 152  2
             metadata.setProperties((Map<String, Metadata>)metadataRepository.get(objectKey));
 153  
             
 154  
             //return the clone
 155  2
             return new Metadata(metadata);
 156  
         }        
 157  
     }    
 158  
     
 159  
     /**
 160  
      * Retreives data object structure from the spring file and caches the properties
 161  
      * 
 162  
      * @param properties
 163  
      * @param fields
 164  
      */
 165  
     protected void loadProperties(Map<String, Metadata> properties, List<DataFieldDescriptor> fields, RecursionCounter counter){
 166  
 
 167  22
         for (DataFieldDescriptor field:fields){
 168  145
             Metadata metadata = new Metadata();
 169  145
             metadata.setWriteAccess(WriteAccess.valueOf(field.getWriteAccess()));
 170  145
             metadata.setDataType(DataType.valueOf(field.getDataType()));
 171  145
             metadata.setAdditionalLookups(field.getAdditionalLookups());
 172  145
             metadata.setLookupContextPath(field.getLookupContextPath());
 173  
             
 174  145
             metadata.setNonServerConstraints(field.getConstraints());
 175  145
             metadata.setName(field.getName());
 176  145
             metadata.setCanEdit(field.isCanEdit());
 177  145
             metadata.setCanView(field.isCanView());
 178  145
             metadata.setCanUnmask(field.isCanUnmask());
 179  145
             metadata.setDefaultValue(convertDefaultValue(metadata.getDataType(), field.getDefaultValue()));
 180  145
             metadata.setInitialLookup(field.getInitialLookup());
 181  
             
 182  145
             if (isRepeating(field)){
 183  19
                 Metadata repeatingMetadata = new Metadata();
 184  19
                 metadata.setDataType(DataType.LIST);
 185  
                 
 186  19
                 repeatingMetadata.setWriteAccess(WriteAccess.ALWAYS);
 187  19
                 repeatingMetadata.setOnChangeRefreshMetadata(false);
 188  19
                 repeatingMetadata.setDataType(DataType.valueOf(field.getDataType()));
 189  
                                 
 190  19
                 if (field.getDataObjectStructure() != null){                   
 191  14
                     repeatingMetadata.setProperties(getProperties(field.getDataObjectStructure(), counter));
 192  
                 }
 193  
                 
 194  19
                 Map<String, Metadata> repeatingProperty = new HashMap<String, Metadata>();
 195  19
                 repeatingProperty.put("*", repeatingMetadata);
 196  19
                 metadata.setProperties(repeatingProperty);
 197  19
             } else if (field.getDataObjectStructure() != null){
 198  25
                 metadata.setProperties(getProperties(field.getDataObjectStructure(), counter));
 199  
             }
 200  
 
 201  145
             properties.put(field.getName(), metadata);
 202  145
         }        
 203  22
     }
 204  
     
 205  
     /** 
 206  
      * This method determines if a field is a repeating field
 207  
      * 
 208  
      * @param field
 209  
      * @return
 210  
      */
 211  
     protected boolean isRepeating(DataFieldDescriptor field){
 212  145
         if (field.getConstraints() != null) {
 213  145
             for (ConstraintMetadata c : field.getConstraints()) {
 214  493
                 if (c.getId() != null && c.getId().equals("repeating")) {
 215  19
                     return true;
 216  
                 }
 217  
             }
 218  
         }
 219  126
         return false;
 220  
     }
 221  
     
 222  
     @SuppressWarnings("unchecked")
 223  
     protected Map<String, Metadata> getProperties(DataObjectStructure dataObjectStructure, RecursionCounter counter){
 224  57
         String objectId = dataObjectStructure.getName();
 225  57
         int hits = counter.increment(objectId);
 226  
         
 227  57
         Map<String, Metadata> properties = null;
 228  
         
 229  57
         if (hits == 1 && metadataRepository.containsKey(objectId)){
 230  34
             properties =  (Map<String, Metadata>)metadataRepository.get(objectId);
 231  23
         } else if (hits < RecursionCounter.MAX_DEPTH){
 232  22
             properties = new HashMap<String, Metadata>();
 233  22
             if (hits == 1){
 234  18
                 metadataRepository.put(objectId, properties);
 235  
             }
 236  22
             loadProperties(properties, dataObjectStructure.getFields(), counter);
 237  
         }
 238  
         
 239  57
         counter.decrement(objectId);
 240  57
         return properties;
 241  
     }
 242  
     
 243  
     
 244  
     /** 
 245  
      * This invokes the appropriate dictionary service to get the object structure and then
 246  
      * converts it to a metadata structure.
 247  
      * 
 248  
      * @param objectKey
 249  
      * @param type
 250  
      * @param state
 251  
      * @return
 252  
      */
 253  
     protected Metadata getMetadataFromDictionaryService(String objectKey, String type, String state){
 254  
 
 255  1
         Metadata metadata = new Metadata();      
 256  
 
 257  1
         ObjectStructure objectStructure = getObjectStructure(objectKey);
 258  1
         State objectState = getObjectState(objectStructure, type, state);
 259  
         
 260  1
         ConstraintDescriptor constraintDescriptor = objectState.getConstraintDescriptor(); 
 261  1
         metadata.setNonServerConstraints(copyConstraints(constraintDescriptor));
 262  
         
 263  1
         List<Field> fields = objectState.getField();
 264  1
         metadata.setProperties(getProperties(fields, type, state));
 265  
         
 266  1
         metadata.setWriteAccess(WriteAccess.ALWAYS);
 267  1
         metadata.setDataType(DataType.DATA);
 268  
         
 269  1
         return metadata;
 270  
     }
 271  
     
 272  
     /** 
 273  
      * This method is used to convert a list of dictionary fields into metadata properties
 274  
      * 
 275  
      * @param fields
 276  
      * @param type
 277  
      * @param state
 278  
      * @return
 279  
      */
 280  
     private Map<String, Metadata> getProperties(List<Field> fields, String type, String state){
 281  1
         Map<String, Metadata> properties = new HashMap<String, Metadata>();
 282  
         
 283  1
         for (Field field:fields){
 284  3
             FieldDescriptor fd = field.getFieldDescriptor();
 285  
             
 286  3
             Metadata metadata = new Metadata();
 287  3
             metadata.setWriteAccess(WriteAccess.ALWAYS);
 288  3
             metadata.setDataType(convertDictionaryDataType(fd.getDataType()));
 289  3
             metadata.setNonServerConstraints(copyConstraints(field.getConstraintDescriptor()));            
 290  
             
 291  
             //Where to get values for defaultValue, lookupMetdata (SearchSelector,fd.getSearch()), 
 292  
                                    
 293  3
             Map<String, Metadata> nestedProperties = null;
 294  3
             if (fd.getDataType().equals("complex")){                                                                  
 295  
                 ObjectStructure objectStructure;
 296  0
                 if (fd.getObjectStructure() != null){
 297  0
                     objectStructure = fd.getObjectStructure();
 298  
                 } else {
 299  0
                     String objectKey = fd.getObjectStructureRef();
 300  0
                     objectStructure = getObjectStructure(objectKey);
 301  
                 }
 302  
 
 303  0
                 State objectState = getObjectState(objectStructure, type, state);
 304  0
                 nestedProperties = getProperties(objectState.getField(), type, state);
 305  
                 
 306  
                 //Cross field constraints for nested object fields? What to do about them?
 307  
                 //ConstraintDescriptor constraintDescriptor = objectState.getConstraintDescriptor()
 308  
 
 309  
             }
 310  
             
 311  
             
 312  3
             if (isRepeating(field)){
 313  0
                 Metadata repeatingMetadata = new Metadata();
 314  0
                 metadata.setDataType(DataType.LIST);
 315  
                 
 316  0
                 repeatingMetadata.setWriteAccess(WriteAccess.ALWAYS);
 317  0
                 repeatingMetadata.setOnChangeRefreshMetadata(false);
 318  0
                 repeatingMetadata.setDataType(convertDictionaryDataType(fd.getDataType()));
 319  
                 
 320  0
                 if (nestedProperties != null){
 321  0
                     repeatingMetadata.setProperties(nestedProperties);
 322  
                 }
 323  
                 
 324  0
                 Map<String, Metadata> repeatingProperty = new HashMap<String, Metadata>();
 325  0
                 repeatingProperty.put("*", repeatingMetadata);
 326  0
                 metadata.setProperties(repeatingProperty);
 327  0
             } else if (nestedProperties != null){
 328  0
                 metadata.setProperties(nestedProperties);
 329  
             }
 330  
             
 331  3
             properties.put(fd.getName(), metadata);
 332  
             
 333  3
         }
 334  
         
 335  1
         return properties;
 336  
     }
 337  
     
 338  
     
 339  
     /** 
 340  
      * This method determines if a field is repeating
 341  
      * 
 342  
      * @param field
 343  
      * @return
 344  
      */
 345  
     protected boolean isRepeating(Field field){
 346  3
         if (field.getConstraintDescriptor() != null && field.getConstraintDescriptor().getConstraint() != null){
 347  3
             for (ConstraintSelector c:field.getConstraintDescriptor().getConstraint()){
 348  3
                 if (c.getKey().equals("repeating")){
 349  0
                     return true;
 350  
                 }
 351  
             }
 352  
         }
 353  3
         return false;
 354  
     }
 355  
     
 356  
     /** 
 357  
      * This method gets the object structure for given objectKey from a dictionaryService
 358  
      * 
 359  
      * @param objectKey
 360  
      * @return
 361  
      */
 362  
     protected ObjectStructure getObjectStructure(String objectKey){
 363  1
         DictionaryService dictionaryService = dictionaryServiceMap.get(objectKey);
 364  
         
 365  1
         return dictionaryService.getObjectStructure(objectKey);
 366  
     }
 367  
     
 368  
     /**
 369  
      * This method retrieves the desire object state for the object structure. 
 370  
      *
 371  
      * @param objectStructure
 372  
      * @param type
 373  
      * @param state
 374  
      * @return
 375  
      */
 376  
     protected State getObjectState(ObjectStructure objectStructure, String type, String state){
 377  
         //This method would not be required if we could just get objectStructure for a particular 
 378  
         //type/state from the dictionary service
 379  1
         for (Type t:objectStructure.getType()){
 380  1
             if (t.getKey().equals(type)){
 381  1
                 for (State s:t.getState()){
 382  1
                     if (s.getKey().equals(state)){
 383  1
                         return s;
 384  
                     }
 385  
                 }
 386  
             }
 387  
         }
 388  
         
 389  0
         return null;
 390  
     }
 391  
            
 392  
     protected List<ConstraintMetadata> copyConstraints(ConstraintDescriptor constraintDescriptor){
 393  4
         List<ConstraintMetadata> constraints = null;
 394  
        
 395  4
         if (constraintDescriptor != null && constraintDescriptor.getConstraint() != null){           
 396  3
             constraints = new ArrayList<ConstraintMetadata>();
 397  
             
 398  
             
 399  3
             for (ConstraintSelector dictConstraint:constraintDescriptor.getConstraint()){
 400  3
                ConstraintMetadata constraintMetadata = new ConstraintMetadata();
 401  
                
 402  3
                constraintMetadata.setId(dictConstraint.getKey());
 403  3
                if (dictConstraint.getMaxLength() != null){
 404  0
                    constraintMetadata.setMaxLength(Integer.valueOf(dictConstraint.getMaxLength()));
 405  
                }
 406  3
                constraintMetadata.setMinLength(dictConstraint.getMinLength());
 407  3
                constraintMetadata.setMinOccurs(dictConstraint.getMinOccurs());
 408  3
                constraintMetadata.setMinValue(dictConstraint.getMinValue());
 409  
                
 410  3
                if (dictConstraint.getValidChars() != null){
 411  0
                    constraintMetadata.setValidChars(dictConstraint.getValidChars().getValue());
 412  
                }
 413  
                
 414  
                //constraintMetadata.setMessageId("kuali.msg.validation." + dictConstraint.getKey());
 415  
                
 416  
                //Skipping cross field constraints (eg. case, occurs, require)
 417  
                
 418  3
               constraints.add(constraintMetadata);
 419  3
            }
 420  
         }
 421  
         
 422  4
        return constraints;
 423  
     }
 424  
     
 425  
     /**
 426  
      * Convert Object value to respective DataType. Method return null for object Value.
 427  
      * @param dataType
 428  
      * @param value
 429  
      * @return
 430  
      */
 431  
     protected Value convertDefaultValue(DataType dataType, Object value){
 432  145
         Value v = null;
 433  145
         if (value instanceof String){
 434  32
             String s = (String)value;
 435  1
             switch (dataType){
 436  
                 case STRING:
 437  29
                     value = new Data.StringValue(s);
 438  29
                     break; 
 439  
                 case BOOLEAN:
 440  3
                     value = new Data.BooleanValue(Boolean.valueOf(s));
 441  3
                     break;
 442  
                 case FLOAT:
 443  0
                     value = new Data.FloatValue(Float.valueOf(s));
 444  0
                     break;
 445  
                 case DATE:
 446  0
                     DateFormat format = new SimpleDateFormat("yyyy-MM-dd");                    
 447  
                     try {
 448  0
                         value = new Data.DateValue(format.parse(s));
 449  0
                     } catch (ParseException e) {
 450  0
                         LOG.error("Unable to get default date value from metadata definition");
 451  0
                     }
 452  0
                     break;
 453  
                 case LONG:
 454  0
                         if (!s.isEmpty()){
 455  0
                                 value = new Data.LongValue(Long.valueOf(s));
 456  
                         }
 457  
                     break;
 458  
                 case DOUBLE:
 459  0
                     value = new Data.DoubleValue(Double.valueOf(s));
 460  0
                     break;
 461  
                 case INTEGER:
 462  0
                     value = new Data.IntegerValue(Integer.valueOf(s));
 463  
                     break;                    
 464  
             }
 465  
         }
 466  
         
 467  145
         return v;
 468  
     }
 469  
     
 470  
     protected DataType convertDictionaryDataType(String dataType){
 471  3
         if ("string".equals(dataType)){
 472  0
             return DataType.STRING;
 473  3
         } else if ("boolean".equals(dataType)){
 474  0
             return DataType.BOOLEAN;
 475  3
         } else if ("integer".equals(dataType)){
 476  0
             return DataType.INTEGER;
 477  3
         } else if ("datetime".equals(dataType)){
 478  0
             return DataType.DATE;
 479  3
         } else if ("complex".equals(dataType)){
 480  0
             return DataType.DATA;
 481  
         }            
 482  
         
 483  3
         return null;        
 484  
     }
 485  
 }