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.common.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.common.assembly.data.ConstraintMetadata;
28  import org.kuali.student.common.assembly.data.Data;
29  import org.kuali.student.common.assembly.data.Metadata;
30  import org.kuali.student.common.assembly.data.Data.DataType;
31  import org.kuali.student.common.assembly.data.Data.Value;
32  import org.kuali.student.common.assembly.data.Metadata.WriteAccess;
33  import org.kuali.student.common.dictionary.old.dto.ConstraintDescriptor;
34  import org.kuali.student.common.dictionary.old.dto.ConstraintSelector;
35  import org.kuali.student.common.dictionary.old.dto.Field;
36  import org.kuali.student.common.dictionary.old.dto.FieldDescriptor;
37  import org.kuali.student.common.dictionary.old.dto.ObjectStructure;
38  import org.kuali.student.common.dictionary.old.dto.State;
39  import org.kuali.student.common.dictionary.old.dto.Type;
40  import org.kuali.student.common.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      final Logger LOG = Logger.getLogger(MetadataServiceImpl.class);
59      
60      private Map<String, Object> metadataRepository = null;
61      
62      private Map<String, DictionaryService> dictionaryServiceMap;
63      
64      private static class RecursionCounter{
65          public static final int MAX_DEPTH = 4;
66          
67          private Map<String, Integer> recursions = new HashMap<String, Integer>();
68          
69          public int increment(String objectName){
70              Integer hits = recursions.get(objectName);
71              
72              if (hits == null){
73                  hits = new Integer(1);
74              } else {
75                  hits++;
76              }
77              recursions.put(objectName, hits);
78              return hits;
79          }
80          
81          public int decrement(String objectName){
82              Integer hits = recursions.get(objectName);
83               if (hits >= 1){
84                   hits--;
85               }
86  
87               recursions.put(objectName, hits);
88               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      public MetadataServiceImpl(String metadataContext){
98          init(metadataContext, (DictionaryService[])null);
99      }
100     
101     public MetadataServiceImpl(DictionaryService...dictionaryServices){        
102         init(null, dictionaryServices);
103     }
104 
105     public MetadataServiceImpl(String metadataContext, DictionaryService...dictionaryServices){        
106         init(metadataContext, dictionaryServices);
107     }
108     
109     @SuppressWarnings("unchecked")
110     private void init(String metadataContext, DictionaryService...dictionaryServices){
111         if (metadataContext != null){
112         	String[] locations = StringUtils.tokenizeToStringArray(metadataContext, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
113     		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(locations);
114                     
115             Map<String, DataObjectStructure> beansOfType = (Map<String, DataObjectStructure>) context.getBeansOfType(DataObjectStructure.class);
116             metadataRepository = new HashMap<String, Object>();
117             for (DataObjectStructure dataObjStr : beansOfType.values()){
118                 metadataRepository.put(dataObjStr.getName(), getProperties(dataObjStr, new RecursionCounter()));
119             }
120         }
121         
122         if (dictionaryServices != null){
123             this.dictionaryServiceMap = new HashMap<String, DictionaryService>();
124             for (DictionaryService d:dictionaryServices){
125                 List<String> objectTypes = d.getObjectTypes();
126                 for(String objectType:objectTypes){
127                     dictionaryServiceMap.put(objectType, d);
128                 }            
129             }            
130         }
131         
132     }
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         if (metadataRepository == null || metadataRepository.get(objectKey) == null){
145             return getMetadataFromDictionaryService(objectKey, type, state);
146         } else {        
147             Metadata metadata = new Metadata ();
148             metadata.setWriteAccess(WriteAccess.ALWAYS);
149             metadata.setOnChangeRefreshMetadata(false);
150             metadata.setDataType(DataType.DATA);
151 
152             metadata.setProperties((Map<String, Metadata>)metadataRepository.get(objectKey));
153             
154             //return the clone
155             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         for (DataFieldDescriptor field:fields){
168             Metadata metadata = new Metadata();
169             metadata.setWriteAccess(WriteAccess.valueOf(field.getWriteAccess()));
170             metadata.setDataType(DataType.valueOf(field.getDataType()));
171             metadata.setAdditionalLookups(field.getAdditionalLookups());
172             metadata.setLookupContextPath(field.getLookupContextPath());
173             
174             metadata.setNonServerConstraints(field.getConstraints());
175             metadata.setName(field.getName());
176             metadata.setCanEdit(field.isCanEdit());
177             metadata.setCanView(field.isCanView());
178             metadata.setCanUnmask(field.isCanUnmask());
179             metadata.setDefaultValue(convertDefaultValue(metadata.getDataType(), field.getDefaultValue()));
180             metadata.setInitialLookup(field.getInitialLookup());
181             
182             if (isRepeating(field)){
183                 Metadata repeatingMetadata = new Metadata();
184                 metadata.setDataType(DataType.LIST);
185                 
186                 repeatingMetadata.setWriteAccess(WriteAccess.ALWAYS);
187                 repeatingMetadata.setOnChangeRefreshMetadata(false);
188                 repeatingMetadata.setDataType(DataType.valueOf(field.getDataType()));
189                                 
190                 if (field.getDataObjectStructure() != null){                   
191                     repeatingMetadata.setProperties(getProperties(field.getDataObjectStructure(), counter));
192                 }
193                 
194                 Map<String, Metadata> repeatingProperty = new HashMap<String, Metadata>();
195                 repeatingProperty.put("*", repeatingMetadata);
196                 metadata.setProperties(repeatingProperty);
197             } else if (field.getDataObjectStructure() != null){
198                 metadata.setProperties(getProperties(field.getDataObjectStructure(), counter));
199             }
200 
201             properties.put(field.getName(), metadata);
202         }        
203     }
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         if (field.getConstraints() != null) {
213             for (ConstraintMetadata c : field.getConstraints()) {
214                 if (c.getId() != null && c.getId().equals("repeating")) {
215                     return true;
216                 }
217             }
218         }
219         return false;
220     }
221     
222     @SuppressWarnings("unchecked")
223     protected Map<String, Metadata> getProperties(DataObjectStructure dataObjectStructure, RecursionCounter counter){
224         String objectId = dataObjectStructure.getName();
225         int hits = counter.increment(objectId);
226         
227         Map<String, Metadata> properties = null;
228         
229         if (hits == 1 && metadataRepository.containsKey(objectId)){
230             properties =  (Map<String, Metadata>)metadataRepository.get(objectId);
231         } else if (hits < RecursionCounter.MAX_DEPTH){
232             properties = new HashMap<String, Metadata>();
233             if (hits == 1){
234                 metadataRepository.put(objectId, properties);
235             }
236             loadProperties(properties, dataObjectStructure.getFields(), counter);
237         }
238         
239         counter.decrement(objectId);
240         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         Metadata metadata = new Metadata();      
256 
257         ObjectStructure objectStructure = getObjectStructure(objectKey);
258         State objectState = getObjectState(objectStructure, type, state);
259         
260         ConstraintDescriptor constraintDescriptor = objectState.getConstraintDescriptor(); 
261         metadata.setNonServerConstraints(copyConstraints(constraintDescriptor));
262         
263         List<Field> fields = objectState.getField();
264         metadata.setProperties(getProperties(fields, type, state));
265         
266         metadata.setWriteAccess(WriteAccess.ALWAYS);
267         metadata.setDataType(DataType.DATA);
268         
269         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         Map<String, Metadata> properties = new HashMap<String, Metadata>();
282         
283         for (Field field:fields){
284             FieldDescriptor fd = field.getFieldDescriptor();
285             
286             Metadata metadata = new Metadata();
287             metadata.setWriteAccess(WriteAccess.ALWAYS);
288             metadata.setDataType(convertDictionaryDataType(fd.getDataType()));
289             metadata.setNonServerConstraints(copyConstraints(field.getConstraintDescriptor()));            
290             
291             //Where to get values for defaultValue, lookupMetdata (SearchSelector,fd.getSearch()), 
292                                    
293             Map<String, Metadata> nestedProperties = null;
294             if (fd.getDataType().equals("complex")){                                                                  
295                 ObjectStructure objectStructure;
296                 if (fd.getObjectStructure() != null){
297                     objectStructure = fd.getObjectStructure();
298                 } else {
299                     String objectKey = fd.getObjectStructureRef();
300                     objectStructure = getObjectStructure(objectKey);
301                 }
302 
303                 State objectState = getObjectState(objectStructure, type, state);
304                 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             if (isRepeating(field)){
313                 Metadata repeatingMetadata = new Metadata();
314                 metadata.setDataType(DataType.LIST);
315                 
316                 repeatingMetadata.setWriteAccess(WriteAccess.ALWAYS);
317                 repeatingMetadata.setOnChangeRefreshMetadata(false);
318                 repeatingMetadata.setDataType(convertDictionaryDataType(fd.getDataType()));
319                 
320                 if (nestedProperties != null){
321                     repeatingMetadata.setProperties(nestedProperties);
322                 }
323                 
324                 Map<String, Metadata> repeatingProperty = new HashMap<String, Metadata>();
325                 repeatingProperty.put("*", repeatingMetadata);
326                 metadata.setProperties(repeatingProperty);
327             } else if (nestedProperties != null){
328                 metadata.setProperties(nestedProperties);
329             }
330             
331             properties.put(fd.getName(), metadata);
332             
333         }
334         
335         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         if (field.getConstraintDescriptor() != null && field.getConstraintDescriptor().getConstraint() != null){
347             for (ConstraintSelector c:field.getConstraintDescriptor().getConstraint()){
348                 if (c.getKey().equals("repeating")){
349                     return true;
350                 }
351             }
352         }
353         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         DictionaryService dictionaryService = dictionaryServiceMap.get(objectKey);
364         
365         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         for (Type t:objectStructure.getType()){
380             if (t.getKey().equals(type)){
381                 for (State s:t.getState()){
382                     if (s.getKey().equals(state)){
383                         return s;
384                     }
385                 }
386             }
387         }
388         
389         return null;
390     }
391            
392     protected List<ConstraintMetadata> copyConstraints(ConstraintDescriptor constraintDescriptor){
393         List<ConstraintMetadata> constraints = null;
394        
395         if (constraintDescriptor != null && constraintDescriptor.getConstraint() != null){           
396             constraints = new ArrayList<ConstraintMetadata>();
397             
398             
399             for (ConstraintSelector dictConstraint:constraintDescriptor.getConstraint()){
400                ConstraintMetadata constraintMetadata = new ConstraintMetadata();
401                
402                constraintMetadata.setId(dictConstraint.getKey());
403                if (dictConstraint.getMaxLength() != null){
404                    constraintMetadata.setMaxLength(Integer.valueOf(dictConstraint.getMaxLength()));
405                }
406                constraintMetadata.setMinLength(dictConstraint.getMinLength());
407                constraintMetadata.setMinOccurs(dictConstraint.getMinOccurs());
408                constraintMetadata.setMinValue(dictConstraint.getMinValue());
409                
410                if (dictConstraint.getValidChars() != null){
411                    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               constraints.add(constraintMetadata);
419            }
420         }
421         
422        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         Value v = null;
433         if (value instanceof String){
434             String s = (String)value;
435             switch (dataType){
436                 case STRING:
437                     value = new Data.StringValue(s);
438                     break; 
439                 case BOOLEAN:
440                     value = new Data.BooleanValue(Boolean.valueOf(s));
441                     break;
442                 case FLOAT:
443                     value = new Data.FloatValue(Float.valueOf(s));
444                     break;
445                 case DATE:
446                     DateFormat format = new SimpleDateFormat("yyyy-MM-dd");                    
447                     try {
448                         value = new Data.DateValue(format.parse(s));
449                     } catch (ParseException e) {
450                         LOG.error("Unable to get default date value from metadata definition");
451                     }
452                     break;
453                 case LONG:
454                 	if (!s.isEmpty()){
455                 		value = new Data.LongValue(Long.valueOf(s));
456                 	}
457                     break;
458                 case DOUBLE:
459                     value = new Data.DoubleValue(Double.valueOf(s));
460                     break;
461                 case INTEGER:
462                     value = new Data.IntegerValue(Integer.valueOf(s));
463                     break;                    
464             }
465         }
466         
467         return v;
468     }
469     
470     protected DataType convertDictionaryDataType(String dataType){
471         if ("string".equals(dataType)){
472             return DataType.STRING;
473         } else if ("boolean".equals(dataType)){
474             return DataType.BOOLEAN;
475         } else if ("integer".equals(dataType)){
476             return DataType.INTEGER;
477         } else if ("datetime".equals(dataType)){
478             return DataType.DATE;
479         } else if ("complex".equals(dataType)){
480             return DataType.DATA;
481         }            
482         
483         return null;        
484     }
485 }