View Javadoc

1   /*
2    * $Id: JSONUtil.java 799110 2009-07-29 22:44:26Z musachy $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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         // read content
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         // status or error code
213         if (serializationParams.getStatusCode() > 0)
214             response.setStatus(serializationParams.getStatusCode());
215         else if (serializationParams.getErrorCode() > 0)
216             response.sendError(serializationParams.getErrorCode());
217 
218         // content type
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             // recurse the entire superclass/interface hierarchy and add in
294             // order encountered
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 }