View Javadoc

1   package org.kuali.student.common.assembly.transform;
2   
3   import java.beans.BeanInfo;
4   import java.beans.Introspector;
5   import java.beans.PropertyDescriptor;
6   import java.lang.reflect.Field;
7   import java.lang.reflect.ParameterizedType;
8   import java.lang.reflect.Type;
9   import java.sql.Time;
10  import java.sql.Timestamp;
11  import java.util.ArrayList;
12  import java.util.Collection;
13  import java.util.Date;
14  import java.util.HashMap;
15  import java.util.HashSet;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.Map.Entry;
20  import java.util.Set;
21  
22  import org.apache.log4j.Logger;
23  import org.kuali.student.common.assembly.data.Data;
24  import org.kuali.student.common.assembly.data.Data.DataType;
25  import org.kuali.student.common.assembly.data.Data.DataValue;
26  import org.kuali.student.common.assembly.data.Data.Key;
27  import org.kuali.student.common.assembly.data.Data.Property;
28  import org.kuali.student.common.assembly.data.Data.StringKey;
29  import org.kuali.student.common.assembly.data.Metadata;
30  
31  
32  public class DefaultDataBeanMapper implements DataBeanMapper {
33  	public static DataBeanMapper INSTANCE = new DefaultDataBeanMapper();
34  	final Logger LOG = Logger.getLogger(DefaultDataBeanMapper.class);	
35  	
36  	/* (non-Javadoc)
37  	 * @see org.kuali.student.core.assembly.data.DataBeanMapper#convertFromBean(java.lang.Object)
38  	 */
39  	public Data convertFromBean(Object value, Metadata metadata) throws Exception{
40  		 Data result = new Data();
41  	        
42  	        if (value != null) {
43                  BeanInfo info = Introspector.getBeanInfo(value.getClass());
44                  PropertyDescriptor[] properties = info.getPropertyDescriptors();
45                   
46                  for (PropertyDescriptor pd : properties) {
47                      String propKey = pd.getName();
48                      Object propValue = pd.getReadMethod().invoke(value, (Object[]) null);
49                      
50                      if ("attributes".equals(propKey)){
51                      	setDataAttributes(result, propValue, metadata);
52                      } else {
53  	                    setDataValue(result, propKey, propValue, metadata);
54                      }
55                  }
56  	        }	
57  		
58  		return result;
59  	}
60  	
61  	/* (non-Javadoc)
62  	 * @see org.kuali.student.core.assembly.data.DataBeanMapper#convertFromData(org.kuali.student.core.assembly.data.Data, java.lang.Class)
63  	 * 
64  	 * FIXME: Probably a good idea to change "clazz" parameter to be "String objectType" to be consistent with convertFromBean method
65  	 */
66  	public Object convertFromData(Data data, Class<?> clazz, Metadata metadata) throws Exception{
67  		Object result = null;
68  		
69  		result = clazz.newInstance();
70          BeanInfo info = Introspector.getBeanInfo(result.getClass());
71          PropertyDescriptor[] properties = info.getPropertyDescriptors();
72  
73          PropertyDescriptor attrProperty = null;
74          
75          Set<Key> staticProperties = new HashSet<Key>();
76  		for (PropertyDescriptor pd : properties) {
77  			if ("attributes".equals(pd.getName())){
78  				//Dynamic attributes will be handled later
79  				attrProperty = pd;
80  			} else {
81  	            StringKey propKey = new StringKey(pd.getName());
82  	            Object propValue = data.get(propKey);
83  	
84  	            //Process a nested object structure or list
85  	            if (propValue instanceof Data){
86  	            	clazz.getFields();
87  	            	if(metadata!=null){
88  	            		if(DataType.LIST.equals(metadata.getDataType())){
89  	            			propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),metadata.getProperties().get("*"));
90  	            		}else{
91  	            			propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),metadata.getProperties().get(propKey.toString()));
92  	            		}
93  	            	}
94  	            	else{
95  	            		propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),null);
96  	            	}
97  	            }else if(metadata!=null&&propValue==null){
98  	            	Metadata fieldMetadata = metadata.getProperties().get(propKey.toString());
99  	            	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 }