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 }