All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.struts2.json.JSONUtil Maven / Gradle / Ivy

There is a newer version: 6.6.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.struts2.json;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.json.annotations.SMDMethod;

import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.WildcardUtil;

/**
 * Wrapper for JSONWriter with some utility methods.
 */
public class JSONUtil {

    public final static String RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
    public static final boolean CACHE_BEAN_INFO_DEFAULT = true;
    
    private static final Logger LOG = LogManager.getLogger(JSONUtil.class);

    private JSONWriter writer;

    public void setWriter(JSONWriter writer) {
        this.writer = writer;
    }

    @Inject
    public void setContainer(Container container) {
        setWriter(container.getInstance(JSONWriter.class, container.getInstance(String.class,
                JSONConstants.JSON_WRITER)));
    }

    /**
     * Serializes an object into JSON.
     *
     * @param object
     *            to be serialized
     * @param cacheBeanInfo
     * 			  Specifies whether to cache bean info in the JSONWriter
     * @return JSON string
     * @throws JSONException in case of error during serialize
     */
    public String serialize(Object object, boolean cacheBeanInfo) throws JSONException {
        writer.setCacheBeanInfo(cacheBeanInfo);
        return writer.write(object);
    }

    /**
     * Serializes an object into JSON, excluding any properties matching any of
     * the regular expressions in the given collection.
     *
     * @param object
     *            to be serialized
     * @param excludeProperties
     *            Patterns matching properties to exclude
     * @param includeProperties
     *            Patterns matching properties to include
     * @param ignoreHierarchy
     *            whether to ignore properties defined on base classes of the
     *            root object
     * @param excludeNullProperties
     *            enable/disable excluding of null properties
     * @return JSON string
     * @throws JSONException in case of error during serialize
     */
    public String serialize(Object object, Collection excludeProperties,
            Collection includeProperties, boolean ignoreHierarchy, boolean excludeNullProperties)
            throws JSONException {
    	return serialize(object, excludeProperties, includeProperties, 
    			ignoreHierarchy, excludeNullProperties, CACHE_BEAN_INFO_DEFAULT);        
    }
    
    /**
     * Serializes an object into JSON, excluding any properties matching any of
     * the regular expressions in the given collection.
     *
     * @param object
     *            to be serialized
     * @param excludeProperties
     *            Patterns matching properties to exclude
     * @param includeProperties
     *            Patterns matching properties to include
     * @param ignoreHierarchy
     *            whether to ignore properties defined on base classes of the
     *            root object
     * @param excludeNullProperties
     *            enable/disable excluding of null properties
     * @param cacheBeanInfo
     * 			  Specifies whether to cache bean info in the JSONWriter
     * @return JSON string
     * @throws JSONException in case of error during serialize
     */
    public String serialize(Object object, Collection excludeProperties,
            Collection includeProperties, boolean ignoreHierarchy, boolean excludeNullProperties,
            boolean cacheBeanInfo)
            throws JSONException {
        writer.setIgnoreHierarchy(ignoreHierarchy);
        writer.setCacheBeanInfo(cacheBeanInfo);
        return writer.write(object, excludeProperties, includeProperties, excludeNullProperties);
    }

    /**
     * Serializes an object into JSON, excluding any properties matching any of
     * the regular expressions in the given collection.
     *
     * @param object
     *            to be serialized
     * @param excludeProperties
     *            Patterns matching properties to exclude
     * @param includeProperties
     *            Patterns matching properties to include
     * @param ignoreHierarchy
     *            whether to ignore properties defined on base classes of the
     *            root object
     * @param enumAsBean
     *            whether to serialized enums a Bean or name=value pair
     * @param excludeNullProperties
     *            enable/disable excluding of null properties
     * @param defaultDateFormat
     *            date format used to serialize dates
     * @return JSON string
     * @throws JSONException in case of error during serialize
     */
    public String serialize(Object object, Collection excludeProperties,
                                   Collection includeProperties, boolean ignoreHierarchy, boolean enumAsBean,
                                   boolean excludeNullProperties, String defaultDateFormat) throws JSONException {
    	return serialize(object, excludeProperties, includeProperties, ignoreHierarchy, enumAsBean, 
    			excludeNullProperties, defaultDateFormat, CACHE_BEAN_INFO_DEFAULT);        
    }
    
    /**
     * Serializes an object into JSON, excluding any properties matching any of
     * the regular expressions in the given collection.
     *
     * @param object
     *            to be serialized
     * @param excludeProperties
     *            Patterns matching properties to exclude
     * @param includeProperties
     *            Patterns matching properties to include
     * @param ignoreHierarchy
     *            whether to ignore properties defined on base classes of the
     *            root object
     * @param enumAsBean
     *            whether to serialized enums a Bean or name=value pair
     * @param excludeNullProperties
     *            enable/disable excluding of null properties
     * @param defaultDateFormat
     *            date format used to serialize dates
     * @param cacheBeanInfo
     * 			  Specifies whether to cache bean info in the JSONWriter
     * @return JSON string
     * @throws JSONException in case of error during serialize
     */
    public String serialize(Object object, Collection excludeProperties,
                                   Collection includeProperties, boolean ignoreHierarchy, boolean enumAsBean,
                                   boolean excludeNullProperties, String defaultDateFormat, boolean cacheBeanInfo) throws JSONException {
        writer.setIgnoreHierarchy(ignoreHierarchy);
        writer.setEnumAsBean(enumAsBean);
        writer.setDateFormatter(defaultDateFormat);
        writer.setCacheBeanInfo(cacheBeanInfo);
        return writer.write(object, excludeProperties, includeProperties, excludeNullProperties);
    }

    /**
     * Serializes an object into JSON to the given writer.
     *
     * @param writer
     *            Writer to serialize the object to
     * @param object
     *            object to be serialized
     * @throws IOException  in case of IO errors
     * @throws JSONException in case of error during serialize
     */
    public void serialize(Writer writer, Object object) throws IOException, JSONException {
        serialize(writer, object, CACHE_BEAN_INFO_DEFAULT);
    }
    
    /**
     * Serializes an object into JSON to the given writer.
     *
     * @param writer
     *            Writer to serialize the object to
     * @param object
     *            object to be serialized
     * @param cacheBeanInfo
     * 			  Specifies whether to cache bean info in the JSONWriter
     * @throws IOException  in case of IO errors
     * @throws JSONException in case of error during serialize
     */
    public void serialize(Writer writer, Object object, boolean cacheBeanInfo) throws IOException, JSONException {
        writer.write(serialize(object, cacheBeanInfo));
    }

    /**
     * Serializes an object into JSON to the given writer, excluding any
     * properties matching any of the regular expressions in the given
     * collection.
     *
     * @param writer
     *            Writer to serialize the object to
     * @param object
     *            object to be serialized
     * @param excludeProperties
     *            Patterns matching properties to ignore
     * @param includeProperties
     *            Patterns matching properties to include
     * @param excludeNullProperties
     *            enable/disable excluding of null properties
     * @throws IOException  in case of IO errors
     * @throws JSONException in case of error during serialize
     */
    public void serialize(Writer writer, Object object, Collection excludeProperties,
            Collection includeProperties, boolean excludeNullProperties) throws IOException,
            JSONException {
    	serialize(writer, object, excludeProperties, includeProperties, excludeNullProperties, CACHE_BEAN_INFO_DEFAULT);
    }
    
    /**
     * Serializes an object into JSON to the given writer, excluding any
     * properties matching any of the regular expressions in the given
     * collection.
     *
     * @param writer
     *            Writer to serialize the object to
     * @param object
     *            object to be serialized
     * @param excludeProperties
     *            Patterns matching properties to ignore
     * @param includeProperties
     *            Patterns matching properties to include
     * @param excludeNullProperties
     *            enable/disable excluding of null properties
     * @param cacheBeanInfo
     * 			  Specifies whether to cache bean info in the JSONWriter        
     * @throws IOException  in case of IO errors
     * @throws JSONException in case of error during serialize
     */
    public void serialize(Writer writer, Object object, Collection excludeProperties,
            Collection includeProperties, boolean excludeNullProperties, boolean cacheBeanInfo) 
            throws IOException, JSONException {
        writer.write(serialize(object, excludeProperties, includeProperties, true, excludeNullProperties, cacheBeanInfo));
    }

    /**
     * Deserializes a object from JSON
     *
     * @param json
     *            string in JSON
     * @return desrialized object
     * @throws JSONException in case of error during serialize
     */
    public static Object deserialize(String json) throws JSONException {
        JSONReader reader = new JSONReader();
        return reader.read(json);
    }

    /**
     * Deserializes a object from JSON
     *
     * @param reader
     *            Reader to read a JSON string from
     * @return deserialized object
     * @throws JSONException
     *             when IOException happens
     */
    public static Object deserialize(Reader reader) throws JSONException {
        // read content
        BufferedReader bufferReader = new BufferedReader(reader);
        String line;
        StringBuilder buffer = new StringBuilder();

        try {
            while ((line = bufferReader.readLine()) != null) {
                buffer.append(line);
            }
        } catch (IOException e) {
            throw new JSONException(e);
        }

        return deserialize(buffer.toString());
    }

    public static void writeJSONToResponse(SerializationParams serializationParams) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        if (StringUtils.isNotBlank(serializationParams.getSerializedJSON()))
            stringBuilder.append(serializationParams.getSerializedJSON());

        if (StringUtils.isNotBlank(serializationParams.getWrapPrefix()))
            stringBuilder.insert(0, serializationParams.getWrapPrefix());
        else if (serializationParams.isWrapWithComments()) {
            stringBuilder.insert(0, "/* ");
            stringBuilder.append(" */");
        } else if (serializationParams.isPrefix())
            stringBuilder.insert(0, "{}&& ");

        if (StringUtils.isNotBlank(serializationParams.getWrapSuffix()))
            stringBuilder.append(serializationParams.getWrapSuffix());

        String json = stringBuilder.toString();

        LOG.debug("[JSON] {}", json);

        HttpServletResponse response = serializationParams.getResponse();

        // status or error code
        if (serializationParams.getStatusCode() > 0)
            response.setStatus(serializationParams.getStatusCode());
        else if (serializationParams.getErrorCode() > 0)
            response.sendError(serializationParams.getErrorCode());

        // content type
        response.setContentType(serializationParams.getContentType() + ";charset="
                + serializationParams.getEncoding());

        if (serializationParams.isNoCache()) {
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("Expires", "0");
            response.setHeader("Pragma", "No-cache");
        }

        if (serializationParams.isGzip()) {
            response.addHeader("Content-Encoding", "gzip");
            GZIPOutputStream out = null;
            InputStream in = null;
            try {
                out = new GZIPOutputStream(response.getOutputStream());
                in = new ByteArrayInputStream(json.getBytes(serializationParams.getEncoding()));
                byte[] buf = new byte[1024];
                int len;
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
            } finally {
                if (in != null)
                    in.close();
                if (out != null) {
                    out.finish();
                    out.close();
                }
            }

        } else {
            response.setContentLength(json.getBytes(serializationParams.getEncoding()).length);
            PrintWriter out = response.getWriter();
            out.print(json);
        }
    }

    public static Set asSet(String commaDelim) {
        if ((commaDelim == null) || (commaDelim.trim().length() == 0))
            return null;
        return TextParseUtil.commaDelimitedStringToSet(commaDelim);
    }

    /**
     * List visible methods carrying the
     *
     * {@literal @}SMDMethod annotation
     *
     * @param clazz
     *            class
     * @param ignoreInterfaces
     *            if true, only the methods of the class are examined. If false,
     *            annotations on every interfaces' methods are examined.
     * @return array of SMD methods
     */
    @SuppressWarnings("unchecked")
    public static Method[] listSMDMethods(Class clazz, boolean ignoreInterfaces) {
        final List methods = new LinkedList<>();
        if (ignoreInterfaces) {
            for (Method method : clazz.getMethods()) {
                SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class);
                if (smdMethodAnnotation != null) {
                    methods.add(method);
                }
            }
        } else {
            // recurse the entire superclass/interface hierarchy and add in
            // order encountered
            JSONUtil.visitInterfaces(clazz, new JSONUtil.ClassVisitor() {
                public boolean visit(Class aClass) {
                    for (Method method : aClass.getMethods()) {
                        SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class);
                        if ((smdMethodAnnotation != null) && !methods.contains(method)) {
                            methods.add(method);
                        }
                    }
                    return true;
                }
            });
        }

        Method[] methodResult = new Method[methods.size()];
        return methods.toArray(methodResult);
    }

    /**
     * Realizes the visit(Class) method called by vistInterfaces for all
     * encountered classes/interfaces
     */
    public static interface ClassVisitor {

        /**
         * Called when a new interface/class is encountered
         *
         * @param aClass
         *            the encountered class/interface
         * @return true if the recursion should continue, false to stop
         *         recursion immediately
         */
        @SuppressWarnings("unchecked")
        boolean visit(Class aClass);
    }

    /**
     * Visit all the interfaces realized by the specified object, its
     * superclasses and its interfaces 
Visitation is performed in the * following order: aClass aClass' interfaces the interface's superclasses * (interfaces) aClass' superclass superclass' interfaces superclass' * interface's superclasses (interfaces) super-superclass and so on
The * Object base class is base excluded. Classes/interfaces are only visited * once each * * @param aClass * the class to start recursing upwards from * @param visitor * this vistor is called for each class/interface encountered * @return true if all classes/interfaces were visited, false if it was * exited early as specified by a ClassVisitor result */ @SuppressWarnings("unchecked") public static boolean visitInterfaces(Class aClass, ClassVisitor visitor) { List classesVisited = new LinkedList<>(); return visitUniqueInterfaces(aClass, visitor, classesVisited); } /* * Recursive method to visit all the interfaces of a class (and its * superclasses and super-interfaces) if they haven't already been visited. *
Always visits itself if it hasn't already been visited * * @param thisClass * the current class to visit (if not already done so) * @param classesVisited * classes already visited * @param visitor * this vistor is called for each class/interface encountered * @return true if recursion can continue, false if it should be aborted */ private static boolean visitUniqueInterfaces(Class thisClass, ClassVisitor visitor, List classesVisited) { boolean okayToContinue = true; if (!classesVisited.contains(thisClass)) { classesVisited.add(thisClass); okayToContinue = visitor.visit(thisClass); if (okayToContinue) { Class[] interfaces = thisClass.getInterfaces(); int index = 0; while ((index < interfaces.length) && (okayToContinue)) { okayToContinue = visitUniqueInterfaces(interfaces[index++], visitor, classesVisited); } if (okayToContinue) { Class superClass = thisClass.getSuperclass(); if ((superClass != null) && (!Object.class.equals(superClass))) { okayToContinue = visitUniqueInterfaces(superClass, visitor, classesVisited); } } } } return okayToContinue; } public static boolean isGzipInRequest(HttpServletRequest request) { return StringUtils.contains(request.getHeader("Accept-Encoding"), "gzip"); } public static final String REGEXP_PATTERN = "regexp"; public static final String WILDCARD_PATTERN = "wildcard"; /* package */ static final String SPLIT_PATTERN = "split"; /* package */ static final String JOIN_STRING = "join"; /* package */ static final String ARRAY_BEGIN_STRING = "array-begin"; /* package */ static final String ARRAY_END_STRING = "array-end"; /* package */ static Map> getIncludePatternData() { Map> includePatternData = new HashMap<>(); Map data = new HashMap<>(); data.put(REGEXP_PATTERN, "\\\\\\."); data.put(WILDCARD_PATTERN, "\\."); includePatternData.put(SPLIT_PATTERN, data); data = new HashMap<>(); data.put(REGEXP_PATTERN, "\\."); data.put(WILDCARD_PATTERN, "."); includePatternData.put(JOIN_STRING, data); data = new HashMap<>(); data.put(REGEXP_PATTERN, "\\["); data.put(WILDCARD_PATTERN, "["); includePatternData.put(ARRAY_BEGIN_STRING, data); data = new HashMap<>(); data.put(REGEXP_PATTERN, "\\]"); data.put(WILDCARD_PATTERN, "]"); includePatternData.put(ARRAY_END_STRING, data); return includePatternData; } private static final Map> defaultIncludePatternData = getIncludePatternData(); public static List processIncludePatterns(Set includePatterns, String type) { return processIncludePatterns(includePatterns, type, defaultIncludePatternData); } /* package */ static List processIncludePatterns(Set includePatterns, String type, Map> includePatternData) { if (includePatterns != null) { List results = new ArrayList<>(includePatterns.size()); Map existingPatterns = new HashMap<>(); for (String pattern : includePatterns) { processPattern(results, existingPatterns, pattern, type, includePatternData); } return results; } else { return null; } } private static void processPattern(List results, Map existingPatterns, String pattern, String type, Map> includePatternData) { // Compile a pattern for each *unique* "level" of the object // hierarchy specified in the regex. String[] patternPieces = pattern.split(includePatternData.get(SPLIT_PATTERN).get(type)); String patternExpr = ""; for (String patternPiece : patternPieces) { patternExpr = processPatternPiece(results, existingPatterns, patternExpr, patternPiece, type, includePatternData); } } private static String processPatternPiece(List results, Map existingPatterns, String patternExpr, String patternPiece, String type, Map> includePatternData) { if (patternExpr.length() > 0) { patternExpr += includePatternData.get(JOIN_STRING).get(type); } patternExpr += patternPiece; // Check for duplicate patterns so that there is no overlap. if (!existingPatterns.containsKey(patternExpr)) { existingPatterns.put(patternExpr, patternExpr); if (isIndexedProperty(patternPiece, type, includePatternData)) { addPattern(results, patternExpr.substring(0, patternExpr.lastIndexOf(includePatternData.get(ARRAY_BEGIN_STRING).get(type))), type); } addPattern(results, patternExpr, type); } return patternExpr; } /* * Add a pattern that does not have the indexed property matching (ie. list\[\d+\] becomes list). */ private static boolean isIndexedProperty(String patternPiece, String type, Map> includePatternData) { return patternPiece.endsWith(includePatternData.get(ARRAY_END_STRING).get(type)); } private static void addPattern(List results, String pattern, String type) { results.add(REGEXP_PATTERN.equals(type) ? Pattern.compile(pattern) : WildcardUtil.compileWildcardPattern(pattern)); if (LOG.isTraceEnabled()) { LOG.trace("Adding include {} expression: {}", (REGEXP_PATTERN.equals(type) ? "property" : "wildcard"), pattern); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy