1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts2.json;
22
23 import java.io.BufferedReader;
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.PrintWriter;
28 import java.io.Reader;
29 import java.io.Writer;
30 import java.lang.reflect.Method;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.regex.Pattern;
36 import java.util.zip.GZIPOutputStream;
37
38 import javax.servlet.http.HttpServletRequest;
39 import javax.servlet.http.HttpServletResponse;
40
41 import org.apache.commons.lang.xwork.StringUtils;
42 import org.apache.struts2.json.annotations.SMDMethod;
43
44 import com.opensymphony.xwork2.util.logging.Logger;
45 import com.opensymphony.xwork2.util.logging.LoggerFactory;
46
47 /***
48 * Wrapper for JSONWriter with some utility methods.
49 */
50 public class JSONUtil {
51 final static String RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
52 private static final Logger LOG = LoggerFactory.getLogger(JSONUtil.class);
53
54 /***
55 * Serializes an object into JSON.
56 *
57 * @param object
58 * to be serialized
59 * @return JSON string
60 * @throws JSONException
61 */
62 public static String serialize(Object object) throws JSONException {
63 JSONWriter writer = new JSONWriter();
64
65 return writer.write(object);
66 }
67
68 /***
69 * Serializes an object into JSON, excluding any properties matching any of
70 * the regular expressions in the given collection.
71 *
72 * @param object
73 * to be serialized
74 * @param excludeProperties
75 * Patterns matching properties to exclude
76 * @param ignoreHierarchy
77 * whether to ignore properties defined on base classes of the
78 * root object
79 * @return JSON string
80 * @throws JSONException
81 */
82 public static String serialize(Object object, Collection<Pattern> excludeProperties,
83 Collection<Pattern> includeProperties, boolean ignoreHierarchy, boolean excludeNullProperties)
84 throws JSONException {
85 JSONWriter writer = new JSONWriter();
86 writer.setIgnoreHierarchy(ignoreHierarchy);
87 return writer.write(object, excludeProperties, includeProperties, excludeNullProperties);
88 }
89
90 /***
91 * Serializes an object into JSON, excluding any properties matching any of
92 * the regular expressions in the given collection.
93 *
94 * @param object
95 * to be serialized
96 * @param excludeProperties
97 * Patterns matching properties to exclude
98 * @param ignoreHierarchy
99 * whether to ignore properties defined on base classes of the
100 * root object
101 * @param enumAsBean
102 * whether to serialized enums a Bean or name=value pair
103 * @return JSON string
104 * @throws JSONException
105 */
106 public static String serialize(Object object, Collection<Pattern> excludeProperties,
107 Collection<Pattern> includeProperties, boolean ignoreHierarchy, boolean enumAsBean,
108 boolean excludeNullProperties) throws JSONException {
109 JSONWriter writer = new JSONWriter();
110 writer.setIgnoreHierarchy(ignoreHierarchy);
111 writer.setEnumAsBean(enumAsBean);
112 return writer.write(object, excludeProperties, includeProperties, excludeNullProperties);
113 }
114
115 /***
116 * Serializes an object into JSON to the given writer.
117 *
118 * @param writer
119 * Writer to serialize the object to
120 * @param object
121 * object to be serialized
122 * @throws IOException
123 * @throws JSONException
124 */
125 public static void serialize(Writer writer, Object object) throws IOException, JSONException {
126 writer.write(serialize(object));
127 }
128
129 /***
130 * Serializes an object into JSON to the given writer, excluding any
131 * properties matching any of the regular expressions in the given
132 * collection.
133 *
134 * @param writer
135 * Writer to serialize the object to
136 * @param object
137 * object to be serialized
138 * @param excludeProperties
139 * Patterns matching properties to ignore
140 * @throws IOException
141 * @throws JSONException
142 */
143 public static void serialize(Writer writer, Object object, Collection<Pattern> excludeProperties,
144 Collection<Pattern> includeProperties, boolean excludeNullProperties) throws IOException,
145 JSONException {
146 writer.write(serialize(object, excludeProperties, includeProperties, true, excludeNullProperties));
147 }
148
149 /***
150 * Deserializes a object from JSON
151 *
152 * @param json
153 * string in JSON
154 * @return desrialized object
155 * @throws JSONException
156 */
157 public static Object deserialize(String json) throws JSONException {
158 JSONReader reader = new JSONReader();
159 return reader.read(json);
160 }
161
162 /***
163 * Deserializes a object from JSON
164 *
165 * @param reader
166 * Reader to read a JSON string from
167 * @return deserialized object
168 * @throws JSONException
169 * when IOException happens
170 */
171 public static Object deserialize(Reader reader) throws JSONException {
172
173 BufferedReader bufferReader = new BufferedReader(reader);
174 String line = null;
175 StringBuilder buffer = new StringBuilder();
176
177 try {
178 while ((line = bufferReader.readLine()) != null) {
179 buffer.append(line);
180 }
181 } catch (IOException e) {
182 throw new JSONException(e);
183 }
184
185 return deserialize(buffer.toString());
186 }
187
188 public static void writeJSONToResponse(SerializationParams serializationParams) throws IOException {
189 StringBuilder stringBuilder = new StringBuilder();
190 if (StringUtils.isNotBlank(serializationParams.getSerializedJSON()))
191 stringBuilder.append(serializationParams.getSerializedJSON());
192
193 if (StringUtils.isNotBlank(serializationParams.getWrapPrefix()))
194 stringBuilder.insert(0, serializationParams.getWrapPrefix());
195 else if (serializationParams.isWrapWithComments()) {
196 stringBuilder.insert(0, "/* ");
197 stringBuilder.append(" */");
198 } else if (serializationParams.isPrefix())
199 stringBuilder.insert(0, "{}&& ");
200
201 if (StringUtils.isNotBlank(serializationParams.getWrapSuffix()))
202 stringBuilder.append(serializationParams.getWrapSuffix());
203
204 String json = stringBuilder.toString();
205
206 if (LOG.isDebugEnabled()) {
207 LOG.debug("[JSON]" + json);
208 }
209
210 HttpServletResponse response = serializationParams.getResponse();
211
212
213 if (serializationParams.getStatusCode() > 0)
214 response.setStatus(serializationParams.getStatusCode());
215 else if (serializationParams.getErrorCode() > 0)
216 response.sendError(serializationParams.getErrorCode());
217
218
219 if (serializationParams.isSmd())
220 response.setContentType("application/json-rpc;charset=" + serializationParams.getEncoding());
221 else
222 response.setContentType(serializationParams.getContentType() + ";charset="
223 + serializationParams.getEncoding());
224
225 if (serializationParams.isNoCache()) {
226 response.setHeader("Cache-Control", "no-cache");
227 response.setHeader("Expires", "0");
228 response.setHeader("Pragma", "No-cache");
229 }
230
231 if (serializationParams.isGzip()) {
232 response.addHeader("Content-Encoding", "gzip");
233 GZIPOutputStream out = null;
234 InputStream in = null;
235 try {
236 out = new GZIPOutputStream(response.getOutputStream());
237 in = new ByteArrayInputStream(json.getBytes());
238 byte[] buf = new byte[1024];
239 int len;
240 while ((len = in.read(buf)) > 0) {
241 out.write(buf, 0, len);
242 }
243 } finally {
244 if (in != null)
245 in.close();
246 if (out != null) {
247 out.finish();
248 out.close();
249 }
250 }
251
252 } else {
253 response.setContentLength(json.getBytes(serializationParams.getEncoding()).length);
254 PrintWriter out = response.getWriter();
255 out.print(json);
256 }
257 }
258
259 public static List<String> asList(String commaDelim) {
260 if ((commaDelim == null) || (commaDelim.trim().length() == 0))
261 return null;
262 List<String> list = new ArrayList<String>();
263 String[] split = commaDelim.split(",");
264 for (int i = 0; i < split.length; i++) {
265 String trimmed = split[i].trim();
266 if (trimmed.length() > 0) {
267 list.add(trimmed);
268 }
269 }
270 return list;
271 }
272
273 /***
274 * List visible methods carrying the
275 *
276 * @SMDMethod annotation
277 *
278 * @param ignoreInterfaces
279 * if true, only the methods of the class are examined. If false,
280 * annotations on every interfaces' methods are examined.
281 */
282 @SuppressWarnings("unchecked")
283 public static Method[] listSMDMethods(Class clazz, boolean ignoreInterfaces) {
284 final List<Method> methods = new LinkedList<Method>();
285 if (ignoreInterfaces) {
286 for (Method method : clazz.getMethods()) {
287 SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class);
288 if (smdMethodAnnotation != null) {
289 methods.add(method);
290 }
291 }
292 } else {
293
294
295 JSONUtil.visitInterfaces(clazz, new JSONUtil.ClassVisitor() {
296 public boolean visit(Class aClass) {
297 for (Method method : aClass.getMethods()) {
298 SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class);
299 if ((smdMethodAnnotation != null) && !methods.contains(method)) {
300 methods.add(method);
301 }
302 }
303 return true;
304 }
305 });
306 }
307
308 Method[] methodResult = new Method[methods.size()];
309 return methods.toArray(methodResult);
310 }
311
312 /***
313 * Realizes the visit(Class) method called by vistInterfaces for all
314 * encountered classes/interfaces
315 */
316 public static interface ClassVisitor {
317
318 /***
319 * Called when a new interface/class is encountered
320 *
321 * @param aClass
322 * the encountered class/interface
323 * @return true if the recursion should continue, false to stop
324 * recursion immediately
325 */
326 @SuppressWarnings("unchecked")
327 boolean visit(Class aClass);
328 }
329
330 /***
331 * Visit all the interfaces realized by the specified object, its
332 * superclasses and its interfaces <p/> Visitation is performed in the
333 * following order: aClass aClass' interfaces the interface's superclasses
334 * (interfaces) aClass' superclass superclass' interfaces superclass'
335 * interface's superclasses (interfaces) super-superclass and so on <p/> The
336 * Object base class is base excluded. Classes/interfaces are only visited
337 * once each
338 *
339 * @param aClass
340 * the class to start recursing upwards from
341 * @param visitor
342 * this vistor is called for each class/interface encountered
343 * @return true if all classes/interfaces were visited, false if it was
344 * exited early as specified by a ClassVisitor result
345 */
346 @SuppressWarnings("unchecked")
347 public static boolean visitInterfaces(Class aClass, ClassVisitor visitor) {
348 List<Class> classesVisited = new LinkedList<Class>();
349 return visitUniqueInterfaces(aClass, visitor, classesVisited);
350 }
351
352 /***
353 * Recursive method to visit all the interfaces of a class (and its
354 * superclasses and super-interfaces) if they haven't already been visited.
355 * <p/> Always visits itself if it hasn't already been visited
356 *
357 * @param thisClass
358 * the current class to visit (if not already done so)
359 * @param classesVisited
360 * classes already visited
361 * @param visitor
362 * this vistor is called for each class/interface encountered
363 * @return true if recursion can continue, false if it should be aborted
364 */
365 private static boolean visitUniqueInterfaces(Class thisClass, ClassVisitor visitor,
366 List<Class> classesVisited) {
367 boolean okayToContinue = true;
368
369 if (!classesVisited.contains(thisClass)) {
370 classesVisited.add(thisClass);
371 okayToContinue = visitor.visit(thisClass);
372
373 if (okayToContinue) {
374 Class[] interfaces = thisClass.getInterfaces();
375 int index = 0;
376 while ((index < interfaces.length) && (okayToContinue)) {
377 okayToContinue = visitUniqueInterfaces(interfaces[index++], visitor, classesVisited);
378 }
379
380 if (okayToContinue) {
381 Class superClass = thisClass.getSuperclass();
382 if ((superClass != null) && (!Object.class.equals(superClass))) {
383 okayToContinue = visitUniqueInterfaces(superClass, visitor, classesVisited);
384 }
385 }
386 }
387 }
388 return okayToContinue;
389 }
390
391 public static boolean isGzipInRequest(HttpServletRequest request) {
392 String header = request.getHeader("Accept-Encoding");
393 return (header != null) && (header.indexOf("gzip") >= 0);
394 }
395 }