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 }