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 }