001 package org.kuali.student.common.assembly.transform; 002 003 import java.beans.BeanInfo; 004 import java.beans.Introspector; 005 import java.beans.PropertyDescriptor; 006 import java.lang.reflect.Field; 007 import java.lang.reflect.ParameterizedType; 008 import java.lang.reflect.Type; 009 import java.sql.Time; 010 import java.sql.Timestamp; 011 import java.util.ArrayList; 012 import java.util.Collection; 013 import java.util.Date; 014 import java.util.HashMap; 015 import java.util.HashSet; 016 import java.util.Iterator; 017 import java.util.List; 018 import java.util.Map; 019 import java.util.Map.Entry; 020 import java.util.Set; 021 022 import org.apache.log4j.Logger; 023 import org.kuali.student.common.assembly.data.Data; 024 import org.kuali.student.common.assembly.data.Data.DataType; 025 import org.kuali.student.common.assembly.data.Data.DataValue; 026 import org.kuali.student.common.assembly.data.Data.Key; 027 import org.kuali.student.common.assembly.data.Data.Property; 028 import org.kuali.student.common.assembly.data.Data.StringKey; 029 import org.kuali.student.common.assembly.data.Metadata; 030 031 032 public class DefaultDataBeanMapper implements DataBeanMapper { 033 public static DataBeanMapper INSTANCE = new DefaultDataBeanMapper(); 034 final Logger LOG = Logger.getLogger(DefaultDataBeanMapper.class); 035 036 /* (non-Javadoc) 037 * @see org.kuali.student.core.assembly.data.DataBeanMapper#convertFromBean(java.lang.Object) 038 */ 039 public Data convertFromBean(Object value, Metadata metadata) throws Exception{ 040 Data result = new Data(); 041 042 if (value != null) { 043 BeanInfo info = Introspector.getBeanInfo(value.getClass()); 044 PropertyDescriptor[] properties = info.getPropertyDescriptors(); 045 046 for (PropertyDescriptor pd : properties) { 047 String propKey = pd.getName(); 048 Object propValue = pd.getReadMethod().invoke(value, (Object[]) null); 049 050 if ("attributes".equals(propKey)){ 051 setDataAttributes(result, propValue, metadata); 052 } else { 053 setDataValue(result, propKey, propValue, metadata); 054 } 055 } 056 } 057 058 return result; 059 } 060 061 /* (non-Javadoc) 062 * @see org.kuali.student.core.assembly.data.DataBeanMapper#convertFromData(org.kuali.student.core.assembly.data.Data, java.lang.Class) 063 * 064 * FIXME: Probably a good idea to change "clazz" parameter to be "String objectType" to be consistent with convertFromBean method 065 */ 066 public Object convertFromData(Data data, Class<?> clazz, Metadata metadata) throws Exception{ 067 Object result = null; 068 069 result = clazz.newInstance(); 070 BeanInfo info = Introspector.getBeanInfo(result.getClass()); 071 PropertyDescriptor[] properties = info.getPropertyDescriptors(); 072 073 PropertyDescriptor attrProperty = null; 074 075 Set<Key> staticProperties = new HashSet<Key>(); 076 for (PropertyDescriptor pd : properties) { 077 if ("attributes".equals(pd.getName())){ 078 //Dynamic attributes will be handled later 079 attrProperty = pd; 080 } else { 081 StringKey propKey = new StringKey(pd.getName()); 082 Object propValue = data.get(propKey); 083 084 //Process a nested object structure or list 085 if (propValue instanceof Data){ 086 clazz.getFields(); 087 if(metadata!=null){ 088 if(DataType.LIST.equals(metadata.getDataType())){ 089 propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),metadata.getProperties().get("*")); 090 }else{ 091 propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),metadata.getProperties().get(propKey.toString())); 092 } 093 } 094 else{ 095 propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),null); 096 } 097 }else if(metadata!=null&&propValue==null){ 098 Metadata fieldMetadata = metadata.getProperties().get(propKey.toString()); 099 if(fieldMetadata != null && fieldMetadata.getDefaultValue() != null){ 100 propValue = fieldMetadata.getDefaultValue().get(); 101 } 102 } 103 104 //Set the bean property 105 if(pd.getWriteMethod() != null & propValue != null){ 106 if(!(propValue instanceof List) && pd.getPropertyType().isAssignableFrom(List.class)){ 107 ArrayList<Object> list = new ArrayList<Object>(1); 108 list.add(propValue); 109 pd.getWriteMethod().invoke(result, list); 110 }else{ 111 pd.getWriteMethod().invoke(result, new Object[] {propValue}); 112 } 113 } 114 115 //Hold onto the property so we know it is not dynamic 116 staticProperties.add(propKey); 117 } 118 } 119 120 //Any fields not processed above doesn't exist as properties for the bean and 121 //will be set as dynamic attributes. 122 Set<Key> keySet = data.keySet(); 123 if (keySet != null && attrProperty != null){ 124 Map<String,String> attributes = new HashMap<String,String>(); 125 for (Key k : keySet) { 126 String keyString = k.toString(); 127 //Obtain the dynamic flag from the dictionary 128 if(metadata==null){ 129 if (!staticProperties.contains(k) && data.get(k) != null && !keyString.startsWith("_run")){ 130 attributes.put((String)k.get(),data.get(k).toString()); 131 } 132 } 133 else { 134 if ((! staticProperties.contains(k)) && 135 (null != data.get(k)) && 136 (! keyString.startsWith("_run")) && 137 (null != metadata.getProperties().get(keyString)) && 138 (metadata.getProperties().get(keyString).isDynamic())) 139 { 140 if (data.get(k) instanceof Data){ 141 attributes.put((String) k.get(), convertDataValueToStringValue((Data)data.get(k))); 142 } else { 143 attributes.put((String) k.get(), data.get(k).toString()); 144 } 145 } 146 } 147 } 148 if (attrProperty.getWriteMethod() != null) { 149 attrProperty.getWriteMethod().invoke(result, new Object[] {attributes}); 150 } 151 } 152 153 return result; 154 } 155 156 157 /** 158 * Processes a nested data map, it checks to see if the data should be converted to 159 * a list structure or simply be processed as a nested complex object structure. 160 * 161 * @param data 162 * @param propField 163 * @return 164 * @throws Exception 165 */ 166 protected Object convertNestedData(Data data, Field propField, Metadata metadata) throws Exception{ 167 Object result = null; 168 169 Class<?> propClass = propField.getType(); 170 if ("java.util.List".equals(propClass.getName())){ 171 //Get the generic type for the list 172 ParameterizedType propType = (ParameterizedType)propField.getGenericType(); 173 Type itemType = propType.getActualTypeArguments()[0]; 174 175 176 List<Object> resultList = new ArrayList<Object>(); 177 for(Iterator<Property> listIter = data.realPropertyIterator(); listIter.hasNext();){ 178 Property listItem = listIter.next(); 179 Object listItemValue = listItem.getValue(); 180 if (listItemValue instanceof Data ){ 181 Data listItemData = (Data)listItemValue; 182 Boolean isDeleted = listItemData.query("_runtimeData/deleted"); 183 if (isDeleted == null || !isDeleted){ 184 if(metadata!=null){ 185 listItemValue = convertFromData((Data)listItemValue, (Class<?>)itemType, metadata.getProperties().get("*")); 186 }else{ 187 listItemValue = convertFromData((Data)listItemValue, (Class<?>)itemType, null); 188 } 189 resultList.add(listItemValue); 190 } 191 } else { 192 resultList.add(listItemValue); 193 } 194 } 195 196 result = resultList; 197 } else { 198 result = convertFromData(data, propClass,metadata); 199 } 200 201 return result; 202 } 203 204 /** 205 * Used to set a simple property value into the data object. 206 * @param data 207 * @param propertyKey 208 * @param value 209 * 210 */ 211 protected void setDataValue(Data data, String propertyKey, Object value, Metadata metadata) throws Exception{ 212 if (isPropertyValid(value)){ 213 if (value instanceof Boolean){ 214 data.set(propertyKey, (Boolean)value); 215 } else if (value instanceof Date){ 216 data.set(propertyKey, (Date)value); 217 } else if (value instanceof Integer){ 218 data.set(propertyKey, (Integer)value); 219 } else if (value instanceof Double){ 220 data.set(propertyKey, (Double)value); 221 } else if (value instanceof Float){ 222 data.set(propertyKey, (Float)value); 223 } else if (value instanceof Long) { 224 data.set(propertyKey, (Long)value); 225 } else if (value instanceof Short){ 226 data.set(propertyKey, (Short)value); 227 } else if (value instanceof String){ 228 data.set(propertyKey, (String)value); 229 } else if (value instanceof Timestamp){ 230 data.set(propertyKey, (Timestamp)value); 231 } else if (value instanceof Time){ 232 data.set(propertyKey, (Time)value); 233 } else if (value instanceof Collection){ 234 //TODO: Determine correct metadata to pass in getCollectionData() instead of null 235 data.set(propertyKey, getCollectionData(value, null)); 236 } else { 237 //TODO: Determine correct metadata to pass into convertFromBean() instead of null 238 Data dataValue = convertFromBean(value, null); 239 data.set(propertyKey, dataValue); 240 } 241 } 242 243 } 244 245 /** 246 * This process the attributes map and sets the attribute key/value pairs into the Data map 247 * 248 * @param data 249 * @param value 250 */ 251 protected void setDataAttributes(Data data, Object value, Metadata metadata) { 252 @SuppressWarnings("unchecked") 253 Map<String, String> attributes = (Map<String, String>)value; 254 255 for (Entry<String, String> entry:attributes.entrySet()){ 256 Metadata fieldMetadata = null; 257 if (metadata != null) { 258 fieldMetadata = metadata.getProperties().get(entry.getKey()); 259 } else { 260 //FIXME: Fix this so this warning never happens !! 261 LOG.warn("Metadata was null while processing property " + entry.getKey()); 262 } 263 if (fieldMetadata != null && DataType.LIST.equals(fieldMetadata.getDataType())){ 264 data.set(entry.getKey(), convertStringToDataValue(entry.getValue())); 265 } else if ("false".equals(entry.getValue())||"true".equals(entry.getValue())){ 266 data.set(entry.getKey(), Boolean.valueOf(entry.getValue())); 267 }else{ 268 data.set(entry.getKey(), entry.getValue()); 269 } 270 } 271 } 272 273 /** 274 * Converts a list represented by a comma delimited string so to a DataValue 275 * 276 * @param stringValue a comma separated list of values 277 * @return DataValue representation of stringValue 278 */ 279 protected Data convertStringToDataValue(String stringValue) { 280 Data data = null; 281 282 if (stringValue != null){ 283 data = new Data(); 284 String[] stringValues = stringValue.split(","); 285 for (String value:stringValues){ 286 data.add(value); 287 } 288 } 289 290 return data; 291 } 292 293 /** 294 * Converts a list represented by DataValue to a list of values as a comma delimited StringValue 295 296 * @param dataValue DataValue representing a list object 297 * @return the list converted to a comma delimited StringValue 298 */ 299 protected String convertDataValueToStringValue(Data data) { 300 StringBuffer sbValue = new StringBuffer(); 301 302 Iterator<Property> propertyIterator = data.realPropertyIterator(); 303 while(propertyIterator.hasNext()){ 304 Property property = propertyIterator.next(); 305 String propertyValue = property.getValue(); 306 sbValue.append(","); 307 sbValue.append(propertyValue); 308 } 309 310 if (sbValue.toString().isEmpty()){ 311 return ""; 312 } else { 313 return sbValue.toString().substring(1); 314 } 315 } 316 317 /** 318 * Used to set a list item value into the data object. 319 * 320 * @param data 321 * @param value 322 */ 323 protected void setDataListValue(Data data, Object value, Metadata metadata) throws Exception{ 324 if (isPropertyValid(value)){ 325 if (value instanceof Boolean){ 326 data.add((Boolean)value); 327 } else if (value instanceof Date){ 328 data.add((Date)value); 329 } else if (value instanceof Integer){ 330 data.add((Integer)value); 331 } else if (value instanceof Double){ 332 data.add((Double)value); 333 } else if (value instanceof Float){ 334 data.add((Float)value); 335 } else if (value instanceof Long) { 336 data.add((Long)value); 337 } else if (value instanceof Short){ 338 data.add((Short)value); 339 } else if (value instanceof String){ 340 data.add((String)value); 341 } else if (value instanceof Timestamp){ 342 data.add((Timestamp)value); 343 } else if (value instanceof Time){ 344 data.add((Time)value); 345 } else if (value instanceof Collection){ 346 //TODO: Find correct metadata to pass in 347 data.add(getCollectionData(value, null)); 348 } else { 349 //TODO: Find correct metadata to pass in 350 Data dataValue = convertFromBean(value, null); 351 data.add(dataValue); 352 } 353 } 354 } 355 356 /** 357 * Returns a data map object representing a collection 358 * 359 * @param value 360 * @return 361 * @throws Exception 362 */ 363 protected Data getCollectionData(Object value, Metadata metadata) throws Exception{ 364 Data result = new Data(); 365 366 if (value instanceof List){ 367 List<?> valueList = (List<?>)value; 368 for (int i=0;i<valueList.size();i++){ 369 Object itemValue = valueList.get(i); 370 setDataListValue(result, itemValue, metadata); 371 } 372 } else { 373 Collection<?> valueCollection = (Collection<?>)value; 374 for (Object o:valueCollection){ 375 setDataListValue(result, o, metadata); 376 } 377 } 378 379 return result; 380 } 381 382 protected boolean isPropertyValid(Object value){ 383 boolean isValid = false; 384 385 if (value != null){ 386 Class<?> clazz = value.getClass(); 387 isValid = !(clazz.isArray() || clazz.isAnnotation() || value instanceof Class); 388 } 389 390 return isValid; 391 } 392 }