001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http.converter.json; 018 019import java.io.IOException; 020import java.io.InputStreamReader; 021import java.io.OutputStreamWriter; 022import java.io.Reader; 023import java.io.Writer; 024import java.lang.reflect.Type; 025import java.nio.charset.Charset; 026import java.nio.charset.StandardCharsets; 027 028import org.springframework.core.GenericTypeResolver; 029import org.springframework.http.HttpHeaders; 030import org.springframework.http.HttpInputMessage; 031import org.springframework.http.HttpOutputMessage; 032import org.springframework.http.MediaType; 033import org.springframework.http.converter.AbstractGenericHttpMessageConverter; 034import org.springframework.http.converter.HttpMessageNotReadableException; 035import org.springframework.http.converter.HttpMessageNotWritableException; 036import org.springframework.lang.Nullable; 037 038/** 039 * Common base class for plain JSON converters, e.g. Gson and JSON-B. 040 * 041 * <p>Note that the Jackson converters have a dedicated class hierarchy 042 * due to their multi-format support. 043 * 044 * @author Juergen Hoeller 045 * @since 5.0 046 * @see GsonHttpMessageConverter 047 * @see JsonbHttpMessageConverter 048 * @see #readInternal(Type, Reader) 049 * @see #writeInternal(Object, Type, Writer) 050 */ 051public abstract class AbstractJsonHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> { 052 053 /** 054 * The default charset used by the converter. 055 */ 056 public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; 057 058 @Nullable 059 private String jsonPrefix; 060 061 062 public AbstractJsonHttpMessageConverter() { 063 super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); 064 setDefaultCharset(DEFAULT_CHARSET); 065 } 066 067 068 /** 069 * Specify a custom prefix to use for JSON output. Default is none. 070 * @see #setPrefixJson 071 */ 072 public void setJsonPrefix(String jsonPrefix) { 073 this.jsonPrefix = jsonPrefix; 074 } 075 076 /** 077 * Indicate whether the JSON output by this view should be prefixed with ")]}', ". 078 * Default is {@code false}. 079 * <p>Prefixing the JSON string in this manner is used to help prevent JSON 080 * Hijacking. The prefix renders the string syntactically invalid as a script 081 * so that it cannot be hijacked. 082 * This prefix should be stripped before parsing the string as JSON. 083 * @see #setJsonPrefix 084 */ 085 public void setPrefixJson(boolean prefixJson) { 086 this.jsonPrefix = (prefixJson ? ")]}', " : null); 087 } 088 089 090 @Override 091 public final Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage) 092 throws IOException, HttpMessageNotReadableException { 093 094 return readResolved(GenericTypeResolver.resolveType(type, contextClass), inputMessage); 095 } 096 097 @Override 098 protected final Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) 099 throws IOException, HttpMessageNotReadableException { 100 101 return readResolved(clazz, inputMessage); 102 } 103 104 private Object readResolved(Type resolvedType, HttpInputMessage inputMessage) 105 throws IOException, HttpMessageNotReadableException { 106 107 Reader reader = getReader(inputMessage); 108 try { 109 return readInternal(resolvedType, reader); 110 } 111 catch (Exception ex) { 112 throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex, inputMessage); 113 } 114 } 115 116 @Override 117 protected final void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) 118 throws IOException, HttpMessageNotWritableException { 119 120 Writer writer = getWriter(outputMessage); 121 if (this.jsonPrefix != null) { 122 writer.append(this.jsonPrefix); 123 } 124 try { 125 writeInternal(object, type, writer); 126 } 127 catch (Exception ex) { 128 throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); 129 } 130 writer.flush(); 131 } 132 133 134 /** 135 * Template method that reads the JSON-bound object from the given {@link Reader}. 136 * @param resolvedType the resolved generic type 137 * @param reader the {@code} Reader to use 138 * @return the JSON-bound object 139 * @throws Exception in case of read/parse failures 140 */ 141 protected abstract Object readInternal(Type resolvedType, Reader reader) throws Exception; 142 143 /** 144 * Template method that writes the JSON-bound object to the given {@link Writer}. 145 * @param object the object to write to the output message 146 * @param type the type of object to write (may be {@code null}) 147 * @param writer the {@code} Writer to use 148 * @throws Exception in case of write failures 149 */ 150 protected abstract void writeInternal(Object object, @Nullable Type type, Writer writer) throws Exception; 151 152 153 private static Reader getReader(HttpInputMessage inputMessage) throws IOException { 154 return new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders())); 155 } 156 157 private static Writer getWriter(HttpOutputMessage outputMessage) throws IOException { 158 return new OutputStreamWriter(outputMessage.getBody(), getCharset(outputMessage.getHeaders())); 159 } 160 161 private static Charset getCharset(HttpHeaders headers) { 162 Charset charset = (headers.getContentType() != null ? headers.getContentType().getCharset() : null); 163 return (charset != null ? charset : DEFAULT_CHARSET); 164 } 165 166}