001    /**
002     * Copyright 2010 The Kuali Foundation Licensed under the
003     * Educational Community License, Version 2.0 (the "License"); you may
004     * not use this file except in compliance with the License. You may
005     * obtain a copy of the License at
006     *
007     * http://www.osedu.org/licenses/ECL-2.0
008     *
009     * Unless required by applicable law or agreed to in writing,
010     * software distributed under the License is distributed on an "AS IS"
011     * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
012     * or implied. See the License for the specific language governing
013     * permissions and limitations under the License.
014     */
015    
016    package org.kuali.student.common.assembly.dictionary.old;
017    
018    import java.text.DateFormat;
019    import java.text.ParseException;
020    import java.text.SimpleDateFormat;
021    import java.util.ArrayList;
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.log4j.Logger;
027    import org.kuali.student.common.assembly.data.ConstraintMetadata;
028    import org.kuali.student.common.assembly.data.Data;
029    import org.kuali.student.common.assembly.data.Metadata;
030    import org.kuali.student.common.assembly.data.Data.DataType;
031    import org.kuali.student.common.assembly.data.Data.Value;
032    import org.kuali.student.common.assembly.data.Metadata.WriteAccess;
033    import org.kuali.student.common.dictionary.old.dto.ConstraintDescriptor;
034    import org.kuali.student.common.dictionary.old.dto.ConstraintSelector;
035    import org.kuali.student.common.dictionary.old.dto.Field;
036    import org.kuali.student.common.dictionary.old.dto.FieldDescriptor;
037    import org.kuali.student.common.dictionary.old.dto.ObjectStructure;
038    import org.kuali.student.common.dictionary.old.dto.State;
039    import org.kuali.student.common.dictionary.old.dto.Type;
040    import org.kuali.student.common.dictionary.service.old.DictionaryService;
041    import org.springframework.context.ConfigurableApplicationContext;
042    import org.springframework.context.support.ClassPathXmlApplicationContext;
043    import org.springframework.util.StringUtils;
044    
045    /**
046     * This class provides metadata lookup services for orchestration objects.
047     * 
048     *  TODO: 
049     *      1) Handle type state configuration & better caching
050     *      2) Differentiate b/w metadata structure required for client vs. assemblers
051     *      3) Namespace collision b/w service dictionaries and orchestration dictionary?      
052     * 
053     * @author Kuali Student Team
054     *
055     */
056    @Deprecated
057    public class MetadataServiceImpl {
058        final Logger LOG = Logger.getLogger(MetadataServiceImpl.class);
059        
060        private Map<String, Object> metadataRepository = null;
061        
062        private Map<String, DictionaryService> dictionaryServiceMap;
063        
064        private static class RecursionCounter{
065            public static final int MAX_DEPTH = 4;
066            
067            private Map<String, Integer> recursions = new HashMap<String, Integer>();
068            
069            public int increment(String objectName){
070                Integer hits = recursions.get(objectName);
071                
072                if (hits == null){
073                    hits = new Integer(1);
074                } else {
075                    hits++;
076                }
077                recursions.put(objectName, hits);
078                return hits;
079            }
080            
081            public int decrement(String objectName){
082                Integer hits = recursions.get(objectName);
083                 if (hits >= 1){
084                     hits--;
085                 }
086    
087                 recursions.put(objectName, hits);
088                 return hits;
089            }
090        }
091        
092        /**
093         * Create a Metadata service initialized using a given classpath metadata context file
094         * 
095         * @param metadataContext the classpath metadata context file
096         */
097        public MetadataServiceImpl(String metadataContext){
098            init(metadataContext, (DictionaryService[])null);
099        }
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    }