View Javadoc

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