Vipin CP
Vipin CP

Reputation: 3807

Replace existing response with new using servlet filter in spring mvc

I want to replace exisiting json response(In some condition) with the new one using filters. What am trying to do is read existing response (JSON) from filter. Modify it with new values and write back to response. But the result shows both the response.

That is , what i have read from response and what i added newly. But i need to replace the old response with new. Code added below.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
{
    try{
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final PrintStream ps = new PrintStream(baos);

        chain.doFilter(request,new HttpServletResponseWrapper((HttpServletResponse)response) {
               @Override
             public ServletOutputStream getOutputStream() throws IOException {
                return new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps)
                );
             }
          @Override
             public  PrintWriter getWriter() throws IOException {
                return new PrintWriter(new DelegatingServletOutputStream (new TeeOutputStream(super.getOutputStream(), ps))
                );
             }
          });
        /* get existing response as string*/
        String respopn=baos.toString(); 
        JSONObject json=new JSONObject(respopn);
        JSONObject dMap=new JSONObject(json.get("dataMap"));
        dMap.put("new", "newValue");
        json.put("dataMap", dMap); // Modified the old datamap with new json
        JsonMapper jsonMap=new JsonMapper();
        jsonMap.setJson(json);
        String str=jsonMap.getJson();
        byte[] responseToSend = restResponseBytes(jsonMap);
        response.getOutputStream().write(responseToSend); // write to response only the new one



    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
    }


   private byte[] restResponseBytes(Object response) throws IOException {
        String serialized = new ObjectMapper().writeValueAsString(response);
        return serialized.getBytes();
    }

Upvotes: 2

Views: 5432

Answers (3)

Lam Vinh
Lam Vinh

Reputation: 724

Just coding as below to return custom response body with error code

final ObjectMapper mapper = new ObjectMapper();
final ObjectNode root = mapper.createObjectNode();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

// set return custom response
ErrorRepsonse error = ErrorRepsonse.builder()
        .status("400")
        .results(List.of(
                ErrorRepsonse.Result.builder()
                        .errorCode("999")
                        .build()
        ))
        .build();
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write(mapper.writeValueAsString(error));
return; // important!!!

Upvotes: 0

vijayakumarpsg587
vijayakumarpsg587

Reputation: 1177

Maybe this could also be considered. So first create a response wrapper. (add the content-type if needed or else you could leave as it is. Mine had a xhml content-type and that prompted me to change the content-type)

Next create wrappers for the printwriter and ServletOutputStream. The wrapper for servlet OutputStream is optional but that depends on you need. Here are the implementations

ByteArrayServletStream.java

public class ByteArrayServletStream extends ServletOutputStream {

    ByteArrayOutputStream baos;

    ByteArrayServletStream(ByteArrayOutputStream baos) {
        this.baos = baos;
    }

    @Override
    public void write(int param) throws IOException {
        baos.write(param);
    }

    @Override
    public boolean isReady() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void setWriteListener(WriteListener listener) {
        // TODO Auto-generated method stub

    }

}

ByteArrayPrinter.java

/**
 * IMplemented own Printer as the new wrapper
 *
 */
public class ByteArrayPrinter {

    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    private PrintWriter pw = new PrintWriter(baos);

    private ServletOutputStream sos = new ByteArrayServletStream(baos);

    public PrintWriter getWriter() {
        return pw;
    }

    public ServletOutputStream getStream() {
        return sos;
    }

    byte[] toByteArray() {
        return baos.toByteArray();
    }
}

SwaggerFilter.java

public class SwaggerFilter implements Filter {

    final String APPLICATION_XHTML = "application/xhtml";
    final String XML_ELEMENT_START = "<Json>";
    final String XML_ELEMENT_END = "</Json>";

    @Override
    public void init(FilterConfig config) throws ServletException {
    }

    /**
     * Filter to remove the extra JSON element and render it
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;

        HttpServletResponse response = (HttpServletResponse) servletResponse;
        ByteArrayPrinter pw = new ByteArrayPrinter();

        // Create a wrapper
        HttpServletResponse wrappedResp = new HttpServletResponseWrapper(response) {

            @Override
            public void setContentType(final String type) {
                super.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            }

            @Override
            public PrintWriter getWriter() {
                return pw.getWriter();
            }

            // set the outputstream content type to JSON
            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                ServletResponse response = this.getResponse();

                String ct = (response != null) ? response.getContentType() : null;
                if (ct != null && ct.contains(APPLICATION_XHTML)) {
                    response.setContentType(ct + AppConstants.CONSTANT_COMMA + MediaType.APPLICATION_JSON_UTF8_VALUE);
                }
                return pw.getStream();
            }

        };
        chain.doFilter(httpRequest, wrappedResp);

        byte[] bytes = pw.toByteArray();
        String respBody = new String(bytes);
        if (respBody.startsWith(XML_ELEMENT_START)) {

            // Instead of using substring made use of stream to identify any occurence of <Json> xml element
            List<String> xmlStringList = Stream.of(respBody).filter((s1) -> s1.contains(XML_ELEMENT_START))
                    // filter the string. Split it by mapping to new arraylist by space
                    .map((stringBeforeSplit) -> Arrays.asList(stringBeforeSplit.split(AppConstants.CONSTANT_SPACE)))
                    // create a new stream of array list strings
                    .flatMap((stringArrayAfterSplit) -> {
                        StringBuffer concatenateStringStream = new StringBuffer();
                        stringArrayAfterSplit.forEach(item -> {

                            concatenateStringStream.append(item);

                        });
                        // remove the <JSON> xml element and return the values
                        return Stream
                                .of(concatenateStringStream.toString().trim()
                                        .replace(XML_ELEMENT_START, AppConstants.CONSTANT_NO_SPACE)
                                        .replace(XML_ELEMENT_END, AppConstants.CONSTANT_NO_SPACE));
                        // collect it as a new list of strings with the xmlelement - <JSON> removed
                    }).collect(Collectors.toList());

            // Join the list to make it one
            String finalString = String.join(AppConstants.CONSTANT_NO_SPACE, xmlStringList);

            // write to the outputstream with JSON mediatype
            response.getOutputStream().write(finalString.getBytes());
        } else {
            response.getOutputStream().write(bytes);
        }

    }

    @Override
    public void destroy() {
    }

}

Upvotes: 0

Rakshith Shetty
Rakshith Shetty

Reputation: 66

I feel below code snippet should work.The problem with above is it is getting appended to the existing data instead of rewriting. We need to create a copy with where the data is stored and copy after manipulating to the original ServletResponse. Hope below code snippet solve your issue.

below is the main do filter method-

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

    ServletResponseWrapperCopier capturingResponseWrapper = new ServletResponseWrapperCopier(
                (HttpServletResponse) response);

    chain.doFilter(request, capturingResponseWrapper);
    try{
        String respopn = capturingResponseWrapper.getCaptureAsString();     
        JSONObject json=new JSONObject(respopn);
        JSONObject dMap=new JSONObject(json.get("dataMap"));
        dMap.put("new", "newValue");
        json.put("dataMap", dMap); // Modified the old datamap with new json
        JsonMapper jsonMap=new JsonMapper();
        jsonMap.setJson(json);
        String str=jsonMap.getJson();
        response.getOutputStream().write(str.getBytes());
    } catch(Exception e){
        log.error("");
    }
}

Below class is used to copy the ServletResponse in the above code snippet

public class ServletResponseWrapperCopier extends HttpServletResponseWrapper{

private final ByteArrayOutputStream capture;
private ServletOutputStream output;
private PrintWriter writer;

public ServletResponseWrapperCopier(HttpServletResponse response) {
    super(response);
    capture = new ByteArrayOutputStream(response.getBufferSize());
}

@Override
public ServletOutputStream getOutputStream() {
    if (writer != null) {
        throw new IllegalStateException(
                "getWriter() has already been called on this response.");
    }

    if (output == null) {
        output = new ServletOutputStream() {
            @Override
            public void write(int b) throws IOException {
                capture.write(b);
            }

            @Override
            public void flush() throws IOException {
                capture.flush();
            }

            @Override
            public void close() throws IOException {
                capture.close();
            }
        };
    }

    return output;
}

@Override
public PrintWriter getWriter() throws IOException {
    if (output != null) {
        throw new IllegalStateException(
                "getOutputStream() has already been called on this response.");
    }

    if (writer == null) {
        writer = new PrintWriter(new OutputStreamWriter(capture,
                getCharacterEncoding()));
    }

    return writer;
}

public byte[] getCaptureAsBytes() throws IOException {
    if (writer != null) {
        writer.close();
    } else if (output != null) {
        output.close();
    }

    return capture.toByteArray();
}

public String getCaptureAsString() throws IOException {
    return new String(getCaptureAsBytes(), getCharacterEncoding());
}

}

Upvotes: 5

Related Questions