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.Set;
20  import java.util.Map.Entry;
21  
22  import org.kuali.student.common.assembly.data.Data;
23  import org.kuali.student.common.assembly.data.Metadata;
24  import org.kuali.student.common.assembly.data.Data.DataType;
25  import org.kuali.student.common.assembly.data.Data.Key;
26  import org.kuali.student.common.assembly.data.Data.Property;
27  import org.kuali.student.common.assembly.data.Data.StringKey;
28  
29  
30  public class DefaultDataBeanMapper implements DataBeanMapper {
31  	public static DataBeanMapper INSTANCE = new DefaultDataBeanMapper();
32  	
33  	/* (non-Javadoc)
34  	 * @see org.kuali.student.core.assembly.data.DataBeanMapper#convertFromBean(java.lang.Object)
35  	 */
36  	public Data convertFromBean(Object value) throws Exception{
37  		 Data result = new Data();
38  	        
39  	        if (value != null) {
40                  BeanInfo info = Introspector.getBeanInfo(value.getClass());
41                  PropertyDescriptor[] properties = info.getPropertyDescriptors();
42                   
43                  for (PropertyDescriptor pd : properties) {
44                      String propKey = pd.getName();
45                      Object propValue = pd.getReadMethod().invoke(value, (Object[]) null);
46                      
47                      if ("attributes".equals(propKey)){
48                      	setDataAttributes(result, propValue);
49                      } else {
50  	                    setDataValue(result, propKey, propValue);
51                      }
52                  }
53  	        }	
54  		
55  		return result;
56  	}
57  	
58  	/* (non-Javadoc)
59  	 * @see org.kuali.student.core.assembly.data.DataBeanMapper#convertFromData(org.kuali.student.core.assembly.data.Data, java.lang.Class)
60  	 */
61  	public Object convertFromData(Data data, Class<?> clazz, Metadata metadata) throws Exception{
62  		Object result = null;
63  		
64  		result = clazz.newInstance();
65          BeanInfo info = Introspector.getBeanInfo(result.getClass());
66          PropertyDescriptor[] properties = info.getPropertyDescriptors();
67  
68          PropertyDescriptor attrProperty = null;
69          
70          Set<Key> staticProperties = new HashSet<Key>();
71  		for (PropertyDescriptor pd : properties) {
72  			if ("attributes".equals(pd.getName())){
73  				//Dynamic attributes will be handled later
74  				attrProperty = pd;
75  			} else {
76  	            StringKey propKey = new StringKey(pd.getName());
77  	            Object propValue = data.get(propKey);
78  	
79  	            //Process a nested object structure or list
80  	            if (propValue instanceof Data){
81  	            	clazz.getFields();
82  	            	if(metadata!=null){
83  	            		if(DataType.LIST.equals(metadata.getDataType())){
84  	            			propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),metadata.getProperties().get("*"));
85  	            		}else{
86  	            			propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),metadata.getProperties().get(propKey.toString()));
87  	            		}
88  	            	}
89  	            	else{
90  	            		propValue = convertNestedData((Data)propValue, clazz.getDeclaredField(propKey.toString()),null);
91  	            	}
92  	            }else if(metadata!=null&&propValue==null){
93  	            	Metadata fieldMetadata = metadata.getProperties().get(propKey.toString());
94  	            	if(fieldMetadata != null && fieldMetadata.getDefaultValue() != null){
95  	            		propValue = fieldMetadata.getDefaultValue().get();	
96  	            	}
97  	            }
98  	            
99  	    		//Set the bean property
100 	    		if(pd.getWriteMethod() != null & propValue != null){
101 	    			if(!(propValue instanceof List) && pd.getPropertyType().isAssignableFrom(List.class)){
102 	    				ArrayList<Object> list = new ArrayList<Object>(1);
103 	    				list.add(propValue);
104 	    				pd.getWriteMethod().invoke(result, list);
105 	    			}else{
106 	    				pd.getWriteMethod().invoke(result, new Object[] {propValue});
107 	    			}
108 	            }
109 	            
110 	    		//Hold onto the property so we know it is not dynamic
111 	    		staticProperties.add(propKey);
112 			}
113 		}
114 		
115 		//Any fields not processed above doesn't exist as properties for the bean and 
116 		//will be set as dynamic attributes.
117 		Set<Key> keySet = data.keySet();
118 		if (keySet != null && attrProperty != null){
119 			Map<String,String> attributes = new HashMap<String,String>();
120             for (Key k : keySet) {
121 				String keyString = k.toString();
122 				//Obtain the dynamic flag from the dictionary
123 				if(metadata==null){
124 					if (!staticProperties.contains(k) && data.get(k) != null && !keyString.startsWith("_run")){
125 						attributes.put((String)k.get(),data.get(k).toString());
126 					}
127 				}
128 				else {
129 				    if ((! staticProperties.contains(k)) &&
130 				        (null != data.get(k)) &&
131 				        (! keyString.startsWith("_run")) &&
132 				        (null != metadata.getProperties().get(keyString)) &&
133 				        (metadata.getProperties().get(keyString).isDynamic()))
134 				    {
135                         attributes.put((String) k.get(), data.get(k).toString());
136 					}
137 				}
138 			}
139             if (attrProperty.getWriteMethod() != null) {
140                 attrProperty.getWriteMethod().invoke(result, new Object[] {attributes});
141             }
142 		}
143 
144 		return result;
145 	}
146 	
147 	
148 	/**
149 	 * Processes a nested data map, it checks to see if the data should be converted to 
150 	 * a list structure or simply be processed as a nested complex object structure.
151 	 * 
152 	 * @param data
153 	 * @param propField
154 	 * @return
155 	 * @throws Exception
156 	 */
157 	protected Object convertNestedData(Data data, Field propField, Metadata metadata) throws Exception{
158 		Object result = null;
159 
160 		Class<?> propClass = propField.getType();
161 		if ("java.util.List".equals(propClass.getName())){
162 			//Get the generic type for the list
163 			ParameterizedType propType = (ParameterizedType)propField.getGenericType();
164 			Type itemType = propType.getActualTypeArguments()[0];
165 			
166 			
167 			List<Object> resultList = new ArrayList<Object>();
168 			for(Iterator<Property> listIter = data.realPropertyIterator(); listIter.hasNext();){
169 				Property listItem = listIter.next();
170 				Object listItemValue = listItem.getValue();
171 				if (listItemValue instanceof Data ){
172 					Data listItemData = (Data)listItemValue;
173 					Boolean isDeleted = listItemData.query("_runtimeData/deleted");
174 					if (isDeleted == null || !isDeleted){
175 						if(metadata!=null){
176 							listItemValue = convertFromData((Data)listItemValue, (Class<?>)itemType, metadata.getProperties().get("*"));
177 						}else{
178 							listItemValue = convertFromData((Data)listItemValue, (Class<?>)itemType, null);
179 						}
180 						resultList.add(listItemValue);
181 					}
182 				} else {
183 					resultList.add(listItemValue);
184 				}
185 			}
186 
187 			result = resultList;
188 		} else {
189 			result = convertFromData(data, propClass,metadata);
190 		}
191 		
192 		return result;
193 	}
194 	
195 	/**
196 	 * Used to set a simple property value into the data object. 
197 	 * @param data
198 	 * @param propertyKey
199 	 * @param value
200 	 * 
201 	 */
202 	protected void setDataValue(Data data, String propertyKey, Object value) throws Exception{
203 		if (isPropertyValid(value)){
204 			if (value instanceof Boolean){
205 				data.set(propertyKey, (Boolean)value);
206 			} else if (value instanceof Date){
207 				data.set(propertyKey, (Date)value);
208 			} else if (value instanceof Integer){
209 				data.set(propertyKey, (Integer)value);
210 			} else if (value instanceof Double){
211 				data.set(propertyKey, (Double)value);
212 			} else if (value instanceof Float){
213 				data.set(propertyKey, (Float)value);
214 			} else if (value instanceof Long) {
215 				data.set(propertyKey, (Long)value);
216 			} else if (value instanceof Short){
217 				data.set(propertyKey, (Short)value);			
218 			} else if (value instanceof String){
219 				data.set(propertyKey, (String)value);
220 			} else if (value instanceof Timestamp){
221 				data.set(propertyKey, (Timestamp)value);
222 			} else if (value instanceof Time){
223 				data.set(propertyKey, (Time)value);
224 			} else if (value instanceof Collection){
225 				data.set(propertyKey, getCollectionData(value));
226 			} else {
227 				Data dataValue = convertFromBean(value);
228 				data.set(propertyKey, dataValue);
229 			}
230 		}
231 		
232 	}
233 	
234 	/**
235 	 * This process the attributes map and sets the attribute key/value pairs into the Data map
236 	 * 
237 	 * @param data
238 	 * @param value
239 	 */
240 	protected void setDataAttributes(Data data, Object value) {
241 		@SuppressWarnings("unchecked")
242 		Map<String, String> attributes = (Map<String, String>)value;
243 		
244 		for (Entry<String, String> entry:attributes.entrySet()){
245 			if("false".equals(entry.getValue())||"true".equals(entry.getValue())){
246 				data.set(entry.getKey(), Boolean.valueOf(entry.getValue()));
247 			}else{
248 				data.set(entry.getKey(), entry.getValue());
249 			}
250 		}
251 	}
252 
253 	/**
254 	 * Used to set a list item value into the data object.
255 	 * 
256 	 * @param data
257 	 * @param value
258 	 */
259 	protected void setDataListValue(Data data, Object value) throws Exception{
260 		if (isPropertyValid(value)){
261 			if (value instanceof Boolean){
262 				data.add((Boolean)value);
263 			} else if (value instanceof Date){
264 				data.add((Date)value);
265 			} else if (value instanceof Integer){
266 				data.add((Integer)value);
267 			} else if (value instanceof Double){
268 				data.add((Double)value);
269 			} else if (value instanceof Float){
270 				data.add((Float)value);
271 			} else if (value instanceof Long) {
272 				data.add((Long)value);
273 			} else if (value instanceof Short){
274 				data.add((Short)value);			
275 			} else if (value instanceof String){
276 				data.add((String)value);
277 			} else if (value instanceof Timestamp){
278 				data.add((Timestamp)value);
279 			} else if (value instanceof Time){
280 				data.add((Time)value);
281 			} else if (value instanceof Collection){
282 				data.add(getCollectionData(value));
283 			} else {
284 				Data dataValue = convertFromBean(value);
285 				data.add(dataValue);			
286 			}
287 		}
288 	}
289 	
290 	/**
291 	 * Returns a data map object representing a collection
292 	 * 
293 	 * @param value
294 	 * @return
295 	 * @throws Exception
296 	 */
297 	protected Data getCollectionData(Object value) throws Exception{
298 		Data result = new Data();
299 		
300 		if (value instanceof List){
301 			List<?> valueList = (List<?>)value;
302 			for (int i=0;i<valueList.size();i++){
303 				Object itemValue = valueList.get(i); 
304 				setDataListValue(result, itemValue);
305 			}
306 		} else {
307 			Collection<?> valueCollection = (Collection<?>)value;
308 			for (Object o:valueCollection){
309 				setDataListValue(result, o);
310 			}
311 		}
312 		
313 		return result;
314 	}
315 	
316 	protected boolean isPropertyValid(Object value){
317 		boolean isValid = false;
318 		
319 		if (value != null){
320 			Class<?> clazz = value.getClass();
321 			isValid = !(clazz.isArray() || clazz.isAnnotation() || value instanceof Class);
322 		}
323 		
324 		return isValid;
325 	}
326 }