1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.shindig.social.core.util;
19
20 import org.apache.shindig.social.core.model.EnumImpl;
21 import org.apache.shindig.social.opensocial.model.Enum;
22 import org.apache.shindig.social.opensocial.service.BeanConverter;
23
24 import com.google.common.collect.ImmutableSet;
25 import com.google.common.collect.Lists;
26 import com.google.common.collect.Maps;
27 import com.google.common.collect.Sets;
28 import com.google.inject.Inject;
29 import com.google.inject.Injector;
30
31 import org.joda.time.DateTime;
32 import org.json.JSONArray;
33 import org.json.JSONException;
34 import org.json.JSONObject;
35
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Method;
38 import java.lang.reflect.ParameterizedType;
39 import java.lang.reflect.Type;
40 import java.util.Date;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Map.Entry;
45 import java.util.Set;
46 import java.util.concurrent.ConcurrentHashMap;
47 import java.util.regex.Matcher;
48 import java.util.regex.Pattern;
49
50 /***
51 * Converts pojos to json objects.
52 * TODO: Replace with standard library
53 */
54 public class BeanJsonConverter implements BeanConverter {
55
56 private static final Object[] EMPTY_OBJECT = {};
57 private static final Set<String> EXCLUDED_FIELDS = ImmutableSet.of("class", "declaringclass");
58 private static final String GETTER_PREFIX = "get";
59 private static final String SETTER_PREFIX = "set";
60
61
62 private static final ConcurrentHashMap<Class,List<MethodPair>> GETTER_METHODS = Maps.newConcurrentHashMap();
63 private static final ConcurrentHashMap<Class,List<MethodPair>> SETTER_METHODS = Maps.newConcurrentHashMap();
64
65 private Injector injector;
66
67 @Inject
68 public BeanJsonConverter(Injector injector) {
69 this.injector = injector;
70 }
71
72 public String getContentType() {
73 return "application/json";
74 }
75
76 /***
77 * Convert the passed in object to a string.
78 *
79 * @param pojo The object to convert
80 * @return An object whos toString method will return json
81 */
82 public String convertToString(final Object pojo) {
83 return convertToJson(pojo).toString();
84 }
85
86 /***
87 * Convert the passed in object to a json object.
88 *
89 * @param pojo The object to convert
90 * @return An object whos toString method will return json
91 */
92 public Object convertToJson(final Object pojo) {
93 try {
94 return translateObjectToJson(pojo);
95 } catch (JSONException e) {
96 throw new RuntimeException("Could not translate " + pojo + " to json", e);
97 }
98 }
99
100 private Object translateObjectToJson(final Object val) throws JSONException {
101 if (val instanceof Object[]) {
102 JSONArray array = new JSONArray();
103 for (Object asd : (Object[]) val) {
104 array.put(translateObjectToJson(asd));
105 }
106 return array;
107
108 } else if (val instanceof List) {
109 JSONArray list = new JSONArray();
110 for (Object item : (List<?>) val) {
111 list.put(translateObjectToJson(item));
112 }
113 return list;
114
115 } else if (val instanceof Map) {
116 JSONObject map = new JSONObject();
117 Map<?, ?> originalMap = (Map<?, ?>) val;
118
119 for (Entry<?, ?> item : originalMap.entrySet()) {
120 map.put(item.getKey().toString(), translateObjectToJson(item.getValue()));
121 }
122 return map;
123
124 } else if (val != null && val.getClass().isEnum()) {
125 return val.toString();
126 } else if (val instanceof String
127 || val instanceof Boolean
128 || val instanceof Integer
129 || val instanceof Date
130 || val instanceof Long
131 || val instanceof Float
132 || val instanceof JSONObject
133 || val instanceof JSONArray
134 || val == null) {
135 return val;
136 }
137
138 return convertMethodsToJson(val);
139 }
140
141 /***
142 * Convert the object to {@link JSONObject} reading Pojo properties
143 *
144 * @param pojo The object to convert
145 * @return A JSONObject representing this pojo
146 */
147 private JSONObject convertMethodsToJson(final Object pojo) {
148 List<MethodPair> availableGetters;
149
150 availableGetters = GETTER_METHODS.get(pojo.getClass());
151 if (availableGetters == null) {
152 availableGetters = getMatchingMethods(pojo, GETTER_PREFIX);
153 GETTER_METHODS.putIfAbsent(pojo.getClass(), availableGetters);
154 }
155
156 JSONObject toReturn = new JSONObject();
157 for (MethodPair getter : availableGetters) {
158 try {
159 Object val = getter.method.invoke(pojo, EMPTY_OBJECT);
160 if (val != null) {
161 toReturn.put(getter.fieldName, translateObjectToJson(val));
162 }
163 } catch (JSONException e) {
164 throw new RuntimeException(errorMessage(pojo, getter), e);
165 } catch (IllegalAccessException e) {
166 throw new RuntimeException(errorMessage(pojo, getter), e);
167 } catch (InvocationTargetException e) {
168 throw new RuntimeException(errorMessage(pojo, getter), e);
169 } catch (IllegalArgumentException e) {
170 throw new RuntimeException(errorMessage(pojo, getter), e);
171 }
172 }
173 return toReturn;
174 }
175
176 private static String errorMessage(Object pojo, MethodPair getter) {
177 return "Could not encode the " + getter.method + " method on "
178 + pojo.getClass().getName();
179 }
180
181 private static final class MethodPair {
182 public Method method;
183 public String fieldName;
184
185 private MethodPair(final Method method, final String fieldName) {
186 this.method = method;
187 this.fieldName = fieldName;
188 }
189 }
190
191
192 private List<MethodPair> getMatchingMethods(Object pojo, String prefix) {
193
194 List<MethodPair> availableGetters = Lists.newArrayList();
195 Method[] methods = pojo.getClass().getMethods();
196 for (Method method : methods) {
197 String name = method.getName();
198 if (!method.getName().startsWith(prefix)) {
199 continue;
200 }
201 int prefixlen = prefix.length();
202
203 String fieldName = name.substring(prefixlen, prefixlen+1).toLowerCase() +
204 name.substring(prefixlen + 1);
205
206 if (EXCLUDED_FIELDS.contains(fieldName.toLowerCase())) {
207 continue;
208 }
209 availableGetters.add(new MethodPair(method, fieldName));
210 }
211 return availableGetters;
212 }
213
214 public <T> T convertToObject(String json, Class<T> className) {
215 String errorMessage = "Could not convert " + json + " to " + className;
216
217 try {
218 T pojo = injector.getInstance(className);
219 return convertToObject(json, pojo);
220 } catch (JSONException e) {
221 throw new RuntimeException(errorMessage, e);
222 } catch (InvocationTargetException e) {
223 throw new RuntimeException(errorMessage, e);
224 } catch (IllegalAccessException e) {
225 throw new RuntimeException(errorMessage, e);
226 } catch (InstantiationException e) {
227 throw new RuntimeException(errorMessage, e);
228 } catch (NoSuchFieldException e) {
229 throw new RuntimeException(errorMessage, e);
230 }
231 }
232
233 private <T> T convertToObject(String json, T pojo)
234 throws JSONException, InvocationTargetException, IllegalAccessException,
235 InstantiationException, NoSuchFieldException {
236
237 if (pojo instanceof String) {
238 pojo = (T) json;
239
240 } else if (pojo instanceof Map) {
241
242
243 Class<?> mapValueClass = String.class;
244
245 JSONObject jsonObject = new JSONObject(json);
246 Iterator<?> iterator = jsonObject.keys();
247 while (iterator.hasNext()) {
248 String key = (String) iterator.next();
249 Object value = convertToObject(jsonObject.getString(key), mapValueClass);
250 ((Map<String, Object>) pojo).put(key, value);
251 }
252
253 } else if (pojo instanceof List) {
254 JSONArray array = new JSONArray(json);
255 for (int i = 0; i < array.length(); i++) {
256 ((List<Object>) pojo).add(array.get(i));
257 }
258 } else {
259 JSONObject jsonObject = new JSONObject(json);
260 List<MethodPair> methods;
261 methods = SETTER_METHODS.get(pojo.getClass());
262 if (methods == null) {
263 methods = getMatchingMethods(pojo, SETTER_PREFIX);
264 SETTER_METHODS.putIfAbsent(pojo.getClass(), methods);
265 }
266
267 for (MethodPair setter : methods) {
268 if (jsonObject.has(setter.fieldName)) {
269 callSetterWithValue(pojo, setter.method, jsonObject, setter.fieldName);
270 }
271 }
272 }
273 return pojo;
274 }
275
276 private <T> void callSetterWithValue(T pojo, Method method,
277 JSONObject jsonObject, String fieldName)
278 throws IllegalAccessException, InvocationTargetException, NoSuchFieldException,
279 JSONException {
280
281 Class<?> expectedType = method.getParameterTypes()[0];
282 Object value = null;
283
284 if (!jsonObject.has(fieldName)) {
285
286 } else if (expectedType.equals(List.class)) {
287 ParameterizedType genericListType
288 = (ParameterizedType) method.getGenericParameterTypes()[0];
289 Type type = genericListType.getActualTypeArguments()[0];
290 Class<?> rawType;
291 Class<?> listElementClass;
292 if (type instanceof ParameterizedType) {
293 listElementClass = (Class<?>)((ParameterizedType)type).getActualTypeArguments()[0];
294 rawType = (Class<?>)((ParameterizedType)type).getRawType();
295 } else {
296 listElementClass = (Class<?>) type;
297 rawType = listElementClass;
298 }
299
300 List<Object> list = Lists.newArrayList();
301 JSONArray jsonArray = jsonObject.getJSONArray(fieldName);
302 for (int i = 0; i < jsonArray.length(); i++) {
303 if (org.apache.shindig.social.opensocial.model.Enum.class
304 .isAssignableFrom(rawType)) {
305 list.add(convertEnum(listElementClass, jsonArray.getJSONObject(i)));
306 } else {
307 list.add(convertToObject(jsonArray.getString(i), listElementClass));
308 }
309 }
310
311 value = list;
312
313 } else if (expectedType.equals(Map.class)) {
314 ParameterizedType genericListType
315 = (ParameterizedType) method.getGenericParameterTypes()[0];
316 Type[] types = genericListType.getActualTypeArguments();
317 Class<?> valueClass = (Class<?>) types[1];
318
319
320
321 Map<String, Object> map = Maps.newHashMap();
322 JSONObject jsonMap = jsonObject.getJSONObject(fieldName);
323
324 Iterator keys = jsonMap.keys();
325 while (keys.hasNext()) {
326 String keyName = (String) keys.next();
327 map.put(keyName, convertToObject(jsonMap.getString(keyName),
328 valueClass));
329 }
330
331 value = map;
332
333 } else if (org.apache.shindig.social.opensocial.model.Enum.class
334 .isAssignableFrom(expectedType)) {
335
336 value = convertEnum(
337 (Class<?>)((ParameterizedType) method.getGenericParameterTypes()[0]).
338 getActualTypeArguments()[0],
339 jsonObject.getJSONObject(fieldName));
340 } else if (expectedType.isEnum()) {
341 if (jsonObject.has(fieldName)) {
342 for (Object v : expectedType.getEnumConstants()) {
343 if (v.toString().equals(jsonObject.getString(fieldName))) {
344 value = v;
345 break;
346 }
347 }
348 if (value == null) {
349 throw new IllegalArgumentException(
350 "No enum value '" + jsonObject.getString(fieldName)
351 + "' in " + expectedType.getName());
352 }
353 }
354 } else if (expectedType.equals(String.class)) {
355 value = jsonObject.getString(fieldName);
356 } else if (expectedType.equals(Date.class)) {
357
358 value = new DateTime(jsonObject.getString(fieldName)).toDate();
359 } else if (expectedType.equals(Long.class) || expectedType.equals(Long.TYPE)) {
360 value = jsonObject.getLong(fieldName);
361 } else if (expectedType.equals(Integer.class) || expectedType.equals(Integer.TYPE)) {
362 value = jsonObject.getInt(fieldName);
363 } else if (expectedType.equals(Boolean.class) || expectedType.equals(Boolean.TYPE)) {
364 value = jsonObject.getBoolean(fieldName);
365 } else if (expectedType.equals(Float.class) || expectedType.equals(Float.TYPE)) {
366 String stringFloat = jsonObject.getString(fieldName);
367 value = new Float(stringFloat);
368 } else {
369
370 value = convertToObject(jsonObject.getJSONObject(fieldName).toString(), expectedType);
371 }
372
373 if (value != null) {
374 method.invoke(pojo, value);
375 }
376 }
377
378 private Object convertEnum(Class<?> enumKeyType, JSONObject jsonEnum)
379 throws JSONException, IllegalAccessException, NoSuchFieldException {
380
381
382 Object value;
383 if (jsonEnum.has(Enum.Field.VALUE.toString())) {
384 Enum.EnumKey enumKey = (Enum.EnumKey) enumKeyType
385 .getField(jsonEnum.getString(Enum.Field.VALUE.toString())).get(null);
386 value = new EnumImpl<Enum.EnumKey>(enumKey,
387 jsonEnum.getString(Enum.Field.DISPLAY_VALUE.toString()));
388 } else {
389 value = new EnumImpl<Enum.EnumKey>(null,
390 jsonEnum.getString(Enum.Field.DISPLAY_VALUE.toString()));
391 }
392 return value;
393 }
394 }