How can I respond string body data without double quotes using ResponseBodyAdvice? - java

I use springboot framework. I need to encrypt body data(from json to encrypted string).
So I used ResponseBodyAdvice.java with #ControllerAdvice but there was a problem that it always responds the encrypted data with double quotes(e.g. "hello")
I need to respond just hello instead of "hello".
#Override
public Object beforeBodyWrite(...) {
response.getHeaders().set("content-type", "text/plain;charset=UTF-8");
//some codes..
String result = "hello";
return result;
}
It responds "hello" (I need to the data without double quotes)
In controller class, it responds just hello(without double quotes).
See the below code.
#ApiOperation(value = "absdfasdf", produces = "text/plain")
#GetMapping("/absd")
public String asdfasdf() {
return "hello";
}

You need to check your MessageConverters to make sure StringMessageConverter is before MappingJackson2HttpMessageConverter. Otherwise, JSON MessageConverter will be selected to serialize string and add the extra double quotes.
#Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// check converter order here
}
}
As the following spring source code, the flow is:
choose a message converter
invoke beforeBodyWrite method in ResponseBodyAdvice
convert message
AbstractMessageConverterMethodProcessor.java
// choose a message converter
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
// invoke beforeBodyWrite
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
// convert message
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
The truth is that we can not change MessageConverter in ResponseBodyAdvice. But we can custom a Dynamic MessageConverter. For example:
public class DynamicMessageConverter implements HttpMessageConverter<Object> {
private final HttpMessageConverter<Object> jsonConverter;
private final HttpMessageConverter<String> stringConverter;
public DynamicMessageConverter(HttpMessageConverter<Object> jsonConverter, HttpMessageConverter<String> stringConverter) {
this.jsonConverter = jsonConverter;
this.stringConverter = stringConverter;
}
#Override
public boolean canWrite(Class clazz, MediaType mediaType) {
return jsonConverter.canWrite(clazz, mediaType) || stringConverter.canWrite(clazz, mediaType);
}
#Override
public List<MediaType> getSupportedMediaTypes() {
List<MediaType> jsonMediaTypes = jsonConverter.getSupportedMediaTypes();
List<MediaType> stringMediaTypes = stringConverter.getSupportedMediaTypes();
List<MediaType> all = new ArrayList<>();
all.addAll(jsonMediaTypes);
all.addAll(stringMediaTypes);
return all;
}
#Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if (o instanceof String) {
stringConverter.write((String) o, contentType, outputMessage);
} else {
jsonConverter.write(o, contentType, outputMessage);
}
}
#Override
public boolean canRead(Class clazz, MediaType mediaType) {
return false;
}
#Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws HttpMessageNotReadableException {
throw new UnsupportedOperationException();
}
}
And then enable it
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
DynamicMessageConverter dynamicMessageConverter = new DynamicMessageConverter(jsonConverter, stringConverter);
converters.add(0, dynamicMessageConverter);
}
Writing directly through response seems more concise.
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
response.getHeaders().set("content-type", "text/plain;charset=UTF-8");
//some codes..
String result = "hello";
try (OutputStream stream = response.getBody()) {
stream.write(result.getBytes("utf-8"));
stream.flush();
} catch (IOException e) {
// log ex
}
return null;
}

Related

How to initialize Multipart request for custom HttpServeletRequest

I am using springboot 2x. Our project is using a Custom HttpServeletRequest which extends HttpServletRequestWrapper and implements MultipartHttpServletRequest. Everything works fine. But when I want to work for file upload, it can't initialized Multipart request. It shows error :
java.lang.IllegalStateException: Multipart request not initialized
My question is, how can I solve this error. How Multipart request will be initialized.
I am giving all code regarding this.
public class XHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest {
public XHttpServletRequest (HttpServletRequest request) {
super(request);
}
private MultiValueMap<String, MultipartFile> multipartFiles;
private String method;
#Override
public String getMethod() {
if (this.method == null) return super.getMethod();
return this.method;
}
public void setMethod(String method) {
this.method = method;
}
private Map<String,String[]> parameters = new LinkedHashMap<String,String[]>();
public void setParameter(String name, String value) {
parameters.put(name, new String[] {value});
}
#Override
public String getParameter(String name) {
if (parameters.get(name) != null) {
return parameters.get(name)[0];
}
HttpServletRequest req = (HttpServletRequest) super.getRequest();
return req.getParameter(name);
}
public Map<String, String[]> getParameterMap() {
Map<String, String[]> result = new LinkedHashMap<String, String[]>();
result.putAll(super.getRequest().getParameterMap());
result.putAll(parameters);
return Collections.<String, String[]>unmodifiableMap(result);
}
public Enumeration<String> getParameterNames() {
Set<String> result = new LinkedHashSet<String>(Collections.list(super.getRequest().getAttributeNames()));
result.addAll(parameters.keySet());
return new Vector<String>(result).elements();
}
public String[] getParameterValues(String name) {
if (parameters.get(name) != null) {
return parameters.get(name);
}
HttpServletRequest req = (HttpServletRequest) super.getRequest();
return req.getParameterValues(name);
}
#Override
public HttpServletRequest getRequest() {
return (HttpServletRequest) super.getRequest();
}
#Override
public HttpMethod getRequestMethod() {
return HttpMethod.resolve(getRequest().getMethod());
}
#Override
public HttpHeaders getRequestHeaders() {
HttpHeaders headers = new HttpHeaders();
Enumeration<String> headerNames = getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, Collections.list(getHeaders(headerName)));
}
return headers;
}
#Override
public HttpHeaders getMultipartHeaders(String s) {
return null;
}
#Override
public Iterator<String> getFileNames() {
return getMultipartFiles().keySet().iterator();
}
#Override
public MultipartFile getFile(String name) {
return getMultipartFiles().getFirst(name);
}
#Override
public List<MultipartFile> getFiles(String name) {
List<MultipartFile> multipartFiles = getMultipartFiles().get(name);
if (multipartFiles != null) {
return multipartFiles;
}
else {
return Collections.emptyList();
}
}
#Override
public Map<String, MultipartFile> getFileMap() {
return getMultipartFiles().toSingleValueMap();
}
#Override
public MultiValueMap<String, MultipartFile> getMultiFileMap() {
return getMultipartFiles();
}
#Override
public String getMultipartContentType(String s) {
return null;
}
/**
* Set a Map with parameter names as keys and list of MultipartFile objects as values.
* To be invoked by subclasses on initialization.
*/
protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
this.multipartFiles =
new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles));
}
/**
* Obtain the MultipartFile Map for retrieval,
* lazily initializing it if necessary.
* #see #initializeMultipart()
*/
protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
if (this.multipartFiles == null) {
initializeMultipart();
}
return this.multipartFiles;
}
/**
* Lazily initialize the multipart request, if possible.
* Only called if not already eagerly initialized.
*/
protected void initializeMultipart() {
throw new IllegalStateException("Multipart request not initialized");
}
}
Another class extends XHttpServletRequest and this is instead of HttpServeletRequest in our project. The following code:
public class YHttpRequest extends MutableHttpServletRequest {
private ByteArrayOutputStream cachedBytes;
public YHttpRequest(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
public List<Map<String, Object>> getListData() throws RequestException {
List<Map<String, Object>> data = new ArrayList<>();
try {
ObjectMapper mapper = new ObjectMapper();
data = mapper.readValue(this.getInputStream(), new TypeReference<ArrayList<LinkedHashMap>>(){});
System.out.println(data);
}
catch (Exception e) {
// System.out.println(e.)
throw new RequestException("Unable to parse request data", e);
}
return data;
}
private Object cachedData = null;
public Object getRawData() throws RequestException {
Object data = new LinkedHashMap<>();
try {
ObjectMapper mapper = new ObjectMapper();
// data = mapper.readValue(this.getInputStream());
try {
data = mapper.readValue(this.getInputStream(), new TypeReference<HashMap>() {
});
}
catch (JsonMappingException e) {
// e.printStackTrace();
}
try {
data = mapper.readValue(this.getInputStream(), new TypeReference<List<HashMap>>() {
});
}
catch (JsonMappingException e) {
// e.printStackTrace();
}
System.out.println(data);
}
catch (Exception e) {
// System.out.println(e.)
throw new RequestException("Unable to parse request data", e);
}
return data;
}
public Object getData() throws RequestException {
if (this.cachedData == null) {
this.cachedData = this.getRawData();
}
return this.cachedData;
}
/* An inputstream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public boolean isFinished() {
return input.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readListener) {
// throw new IOException("zubair says: Method not implemented in Cached Servlet Input Stream class");
}
#Override
public int read() throws IOException {
return input.read();
}
}
// Storage for Path variable
private Map<String, Object> pathVariableMap = null;
public Map<String, Object> getPathVariableMap() {
if (this.pathVariableMap == null) {
this.pathVariableMap = new LinkedHashMap<>();
this.pathVariableMap.putAll((Map<? extends String, ?>) this.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
}
return this.pathVariableMap;
}
public Object getPathVariable(String key) {
return this.getPathVariableMap().get(key);
}
public FurinaHttpRequest setPathVariable(String key, Object value) {
this.getPathVariableMap().put(key, value);
return this;
}
public FurinaHttpRequest clearPathVariableMap() {
this.getPathVariableMap().clear();
return this;
}
}
The controller code:
public String handleFileUpload(YHttpRequest request) {
if (request.getMethod().equalsIgnoreCase("GET")){
return "{}";
}
Map<String, MultipartFile> file= request.getFileMap();
try {
for(Map.Entry<String, MultipartFile> entry : file.entrySet()){
storageService.store(entry.getValue());
//model.addAttribute("message", "You successfully uploaded " + entry.getValue().getOriginalFilename() + "!");
files.add(entry.getValue().getOriginalFilename());
}
} catch (Exception e) {
//model.addAttribute("message", "FAIL to upload !");
}
return "{}";
}
This will work
public String handleFileUpload(YHttpRequest request) {
if (request.getMethod().equalsIgnoreCase("GET")){
return "{}";
}
StandardMultipartHttpServletRequest standardMultipartHttpServletRequest = new StandardMultipartHttpServletRequest(request);
Map<String, MultipartFile> file= request.getFileMap();
try {
for(Map.Entry<String, MultipartFile> entry : file.entrySet()){
storageService.store(entry.getValue());
//model.addAttribute("message", "You successfully uploaded " + entry.getValue().getOriginalFilename() + "!");
files.add(entry.getValue().getOriginalFilename());
}
} catch (Exception e) {
//model.addAttribute("message", "FAIL to upload !");
}
return "{}";
}

Jackson: Adjust ObjectMapper to ignore specific attributes from a class

I'm writing tests for RESTful services in a legacy application using Resteasy Client and Embedded Jaxrs Server. I'm facing problems with Serialization/Deserialization using Jackson with a peculiar Entity, which I'm not supposed to do any changes. This Entity has:
A public "getter" that only performs logic (no equivalent field) and should be serializable, but not deserializable;
A private field used for certain controls and persistence, but without public getter/setter, shouldn't be Serialized/Deserialized;
While in the application our Front-end never sends those attributes back (so they're never deserialized), in my unit tests they are failing due to "org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field". If both properties above have their public getters/setters the test won't fail, but then again I'm not supposed to do changes to the code and those methods would break the architeture of those classes.
I've tweaked my Object Mapper but no configuration was able to deal with this scenario. Either one or another ends up being not found. Is there a way to turn of serialization in those cases by adjusting only my Object Mapper to ignore those properties? Or is it mandatory to have the code altered somehow?
Thanks!
(Small sample)
// Entity
public class Entidade {
private int numero;
private String dado;
private boolean naoUsado = false;
public Entidade() { /**/ }
public Entidade(int numero, String dado) {
this.numero = numero;
this.dado = dado;
}
public int getNumero() {
return numero;
}
public void setNumero(int numero) {
this.numero = numero;
}
public String getDado() {
return dado;
}
public void setDado(String dado) {
this.dado = dado;
}
public String getTeste() {
return "Regra de negócio de leitura";
}
}
// Embedded Server Helper
public class ServidorEmbarcadoRestEasy {
private static final int POOL_SIZE = 9999;
private static final int PORTA = 5555;
private static final String HOST = "localhost";
private TJWSEmbeddedJaxrsServer servidor;
private ResteasyClient resteasyClient;
private ServidorEmbarcadoRestEasy(Object webservice, Class<?>... providers) {
this.servidor = new TJWSEmbeddedJaxrsServer();
this.resteasyClient = new ResteasyClientBuilder().
connectionPoolSize(POOL_SIZE).
connectionCheckoutTimeout(100, TimeUnit.MILLISECONDS).
build();
servidor.setPort(PORTA);
servidor.setBindAddress(HOST);
servidor.getDeployment().getResources().add(webservice);
servidor.setSecurityDomain(null);
servidor.start();
ResteasyProviderFactory factory = servidor.getDeployment().getDispatcher().getProviderFactory();
for (Class<?> c : providers) {
factory.registerProvider(c);
}
}
public static ServidorEmbarcadoRestEasy iniciar(Object webservice, Class<?>... providers) {
ServidorEmbarcadoRestEasy servidorEmbarcado = new ServidorEmbarcadoRestEasy(webservice, providers);
return servidorEmbarcado;
}
public void fechar() {
servidor.stop();
}
private String getUrlCompleta(String uri) {
final String base = "http://%s:%s/%s";
return String.format(base, HOST, PORTA, uri);
}
private Builder request(String uri, List<String> queryParams) {
String urlCompleta = getUrlCompleta(uri);
ResteasyWebTarget target = resteasyClient.target(urlCompleta);
for (String s : queryParams) {
String[] chaveValor = s.split("=");
String chave = chaveValor[0];
String valor = chaveValor[1];
target = target.queryParam(chave, valor);
}
return target.request();
}
private Builder request(String uri) {
if (uri.contains("?")) {
String[] urlFracionada = uri.split("\\?");
String baseUri = urlFracionada[0];
String paramString = urlFracionada[1];
String[] paramArray = paramString.split("&");
List<String> parametros = new ArrayList<>(Arrays.asList(paramArray));
if (!parametros.isEmpty()) {
return request(baseUri, parametros);
}
}
String urlCompleta = getUrlCompleta(uri);
return resteasyClient.target(urlCompleta).request();
}
public Response get(String url) {
return request(url).buildGet().invoke();
}
public Response delete(String url) {
return request(url).buildDelete().invoke();
}
public Response put(String url, Object payload) {
return request(url).buildPut(Entity.json(payload)).invoke();
}
public Response post(String url, Object payload) {
return request(url).buildPost(Entity.json(payload)).invoke();
}
}
// Jackson Provider
public class JacksonTestsProvider extends JacksonJsonProvider {
public static ObjectMapper getMapper() {
return new ObjectMapper()
.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
#Override
public void writeTo(Object arg0, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4,
MultivaluedMap<String, Object> arg5, OutputStream arg6) throws IOException {
super.setMapper(getMapper());
super.writeTo(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
}
#Override
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException {
super.setMapper(getMapper());
return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
}
}
// Unit Tests
public class ServidorEmbarcadoRestEasyTest {
#Path("meuservico")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public static class ServicoTeste {
private Map<Integer, Entidade> entidades = new HashMap<>();
public ServicoTeste() {
for (int i = 1; i <= 100; i++) {
entidades.put(i, new Entidade(i, "Teste " + i));
}
}
#GET
#Path("minha-entidade")
public Response getEntidade() {
return Response.ok(new Entidade(9999, "Nova entidade")).build();
}
#GET
#Path("minha-entidade/{id}")
public Response getEntidade(#PathParam("id") Integer id) {
Entidade retorno = entidades.get(id);
if (retorno == null) {
return Response.status(Status.BAD_REQUEST).build();
} else {
return Response.ok(retorno).build();
}
}
}
private static ServidorEmbarcadoRestEasy servidor;
private static ServicoTeste servico;
#BeforeClass
public static void setUpBeforeClass() throws Exception {
servico = new ServicoTeste();
servidor = ServidorEmbarcadoRestEasy.iniciar(servico,
JacksonTestsProvider.class);
}
#AfterClass
public static void tearDownAfterClass() throws Exception {
servidor.fechar();
}
#Test
public void deveFazerGetComSucesso() {
Response response = servidor.get("meuservico/minha-entidade");
assertEquals(Status.OK.getStatusCode(), response.getStatus());
Entidade entidade = response.readEntity(Entidade.class);
assertEquals(9999, entidade.getNumero());
assertEquals("Nova entidade", entidade.getDado());
response = servidor.get("meuservico/minha-entidade/98");
assertEquals(Status.OK.getStatusCode(), response.getStatus());
entidade = response.readEntity(Entidade.class);
assertEquals(98, entidade.getNumero());
assertEquals("Teste 98", entidade.getDado());
}
}

How to deserialize JSON object from generic type in Java?

I am trying to map JSON response as below:
{
object: {
id: 1
name: "my name"
email: "username#mail.com"
username: "username"
password: "password"
mobile: "##########"
fbAccessToken: "----------"
img: null
}
errorMessage: ""
successMessage: ""
technicalErrorMessage: ""
error: false
}
so I wrote this method:
private <T> ResponseEntity<T> processedRequest(HttpRequestBase requestBase, Class<T> tClass) throws IOException {
HttpResponse response = httpClient.execute(requestBase);
HttpEntity entity = response.getEntity();
Reader reader = new InputStreamReader(entity.getContent());
Type type = new TypeToken<ResponseEntity<T>>() {}.getType();
ResponseEntity<T> responseEntity = gson.fromJson(reader, type);
return responseEntity;
}
based on ResponseEntity class:
public class ResponseEntity<T> {
private T object;
private boolean isError;
private String errorMessage;
private String successMessage;
private String technicalErrorMessage;
public ResponseEntity() {
setSuccessMessage("");
setError(false);
setErrorMessage("");
setTechnicalErrorMessage("");
}
public T getObject() {
return object;
}
public void setObject(T object) {
this.object = object;
}
public boolean isError() {
return isError;
}
public void setError(boolean error) {
this.isError = error;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getTechnicalErrorMessage() {
return technicalErrorMessage;
}
public void setTechnicalErrorMessage(String technicalErrorMessage) {
this.technicalErrorMessage = technicalErrorMessage;
}
public String getSuccessMessage() {
return successMessage;
}
public void setSuccessMessage(String successMessage) {
this.successMessage = successMessage;
}}
but I am getting result as ResponseEntity<LinkedTreeMap> and the object is map of (Key->Value) not the actual mapped object that send by the Type ResponseEntity<T>.
The image below is what appeared in the debugger:
How GSON should understand, which class it should use in place of T and what is the "actual mapped object that send by the Type" to fill it's fields? We have type erasure for generics in Java, so no way in runtime to understand what it T. No way, so gson just uses generic Map<String, String>().
Take a look at responses to this question, it's the same situation.

Empty body getting sent on using PUT method through android Volley

I am using Volley to fetch data from a REST service. So there are two methods , One is POST which creates an object on server and other is PUT which basically updates the same object.I use the same code for making these requests.I just change the Method which is passed in Volley.The app works fine through POST request with the correct data sent in the body, but it encounters a problem with PUT request where it randomly sends no data in the body.
I shifted to a custom Request object as suggested by Google but it is still not working.
int method = Request.Method.POST;
String objID = sharedPreferences.getString("objID", null);
String Url = "/api_url";
if (objID != null)
{
Url += objID + "/";
method = Request.Method.PUT;
}
try
{
JSONObject objJSON = new JSONObject();
objJSON.put("name", "new_name");
}
catch (JSONException e)
{
// Handle Execption
}
GsonRequest request = new GsonRequest<CustomObj>(method, Url, CustomObj.class,null, null, objJSON.toString(),
new Response.Listener<CustomObj>()
{
#Override
public void onResponse(CartObj cartObj)
{
// Handling Logic
}
}
,
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError volleyError)
{
// Handle Error
}
});
VolleySingleton.getInstance(this).addToRequestQueue(request);
GsonRequest is my Custom Class Extending Request Object
public class GsonRequest<T> extends Request<T>
{
private final Gson gson = new Gson();
private final Class<T> clazz;
private final Map<String, String> headers;
private final Listener<T> listener;
private final Map<String, String> params;
private final String body;
private ErrorListener mErrorListener;
/**
* Make a GET request and return a parsed object from JSON.
*
* #param url URL of the request to make
* #param clazz Relevant class object, for Gson's reflection
* #param headers Map of request headers
*/
public GsonRequest(int method, String url, Class<T> clazz, Map<String, String> headers, Map<String, String> params, String body, Listener<T> listener, ErrorListener errorListener)
{
super(method, url, errorListener);
this.clazz = clazz;
this.headers = headers;
this.listener = listener;
this.params = params;
this.body = body;
this.mErrorListener = errorListener;
}
public GsonRequest(int method, String url, Class<T> clazz, Map<String, String> headers, Map<String, String> params, Listener<T> listener, ErrorListener errorListener)
{
this(method, url, clazz, headers, params, null, listener, errorListener);
}
public GsonRequest(int method, String url, Class<T> clazz, Map<String, String> headers, Listener<T> listener, ErrorListener errorListener)
{
this(method, url, clazz, headers, null, null, listener, errorListener);
}
#Override
public byte[] getBody() throws AuthFailureError
{
return body != null ? body.getBytes() : super.getBody();
}
#Override
protected Map<String, String> getParams() throws AuthFailureError
{
return params != null ? params : super.getParams();
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError
{
return headers != null ? headers : super.getHeaders();
}
#Override
protected void deliverResponse(T response)
{
listener.onResponse(response);
}
#Override
public void deliverError(VolleyError error)
{
mErrorListener.onErrorResponse(error);
}
#SuppressWarnings("unchecked")
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response)
{
try
{
String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
return Response.success(gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response));
}
catch (UnsupportedEncodingException e)
{
return Response.error(new ParseError(e));
}
catch (JsonSyntaxException e)
{
return Response.error(new ParseError(e));
}
}
}
I am using GSON to parse my Data , and CustomObj is a Mapping Object for volley.
First of all all make sure the url you're building (where you check if objID is not null) is correct.
Secondly, you can try to call getBytes() method with a charset.
In the request override getBody() method:
#Override
public byte[] getBody() throws AuthFailureError {
try {
return body != null ? body.getBytes(getParamsEncoding()) : super.getBody();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return super.getBody();
}
Hope this will help.
Edit:
You can also try to override getBodyContentType() method:
#Override
public String getBodyContentType() {
return "application/json; charset=" + getParamsEncoding();
}

No Response Body with HttpMessageConverter invoked by ControllerAdvice

EDIT: The error was in the client not the server. The response body was getting written, but the client was not reading it on a 400 response.
I have a custom message converter to produce text/csv, application/csv from an ErrorResponse object. It works as expected when the ErrorResponse is returned directly from a #RequestMapping annotated method, but returns no response body when ErrorResponse is return from an #ExceptionHandler annotated method in a #ControllerAdvice object. I have verified that the message converter writerInternal method is being called and is writing to the response body, but is never makes it back to the client.
ErrorResponse:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="response")
public class ErrorResponse {
private String statusCode;
private String userMessage;
private String developerMessage;
public String getStatusCode() {
return statusCode;
}
public void setStatusCode(final String statusCode) {
this.statusCode = statusCode;
}
public String getUserMessage() {
return userMessage;
}
public void setUserMessage(final String userMessage) {
this.userMessage = userMessage;
}
public String getDeveloperMessage() {
return developerMessage;
}
public void setDeveloperMessage(final String developerMessage) {
this.developerMessage = developerMessage;
}
public ErrorResponse() {
super();
}
public ErrorResponse(final String statusCode, final String userMessage, final String developerMessage) {
super();
this.statusCode = statusCode;
this.userMessage = userMessage;
this.developerMessage = developerMessage;
}
}
MessageConverter:
public class ErrorResponseCsvMessageConverter extends AbstractHttpMessageConverter<ErrorResponse> {
public ErrorResponseCsvMessageConverter() {
super(new MediaType("application", "csv", Charset.forName("UTF-8")),
new MediaType("text", "csv", Charset.forName("UTF-8")),
MediaType.TEXT_PLAIN);
}
#Override
protected ErrorResponse readInternal(final Class<? extends ErrorResponse> clazz, final HttpInputMessage httpInputMessage)
throws IOException, HttpMessageNotReadableException {
// not supported
return null;
}
#Override
protected boolean supports(final Class<?> clazz) {
return ErrorResponse.class.isAssignableFrom(clazz);
}
#Override
protected void writeInternal(final ErrorResponse errorResponse, final HttpOutputMessage httpOutputMessage)
throws IOException, HttpMessageNotWritableException {
System.out.println(errorResponse);
try(CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(httpOutputMessage.getBody(), "UTF-8"))) {
csvWriter.writeNext(new String[] { "statusCode", "userMessage", "developerMessage" });
csvWriter.writeNext(new String[] {
errorResponse.getStatusCode(),
errorResponse.getUserMessage(),
errorResponse.getDeveloperMessage() });
}
}
}
Controller Advice:
...
#ExceptionHandler(MissingServletRequestParameterException.class)
#ResponseBody()
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMissingParamterException(final HttpServletRequest request, final HttpServletResponse httpServletResponse, final MissingServletRequestParameterException e) {
LOG.warn("Bad Request:" +
request.getRequestURI() +
((request.getQueryString()==null) ? "" : "?" + request.getQueryString()));
return new ErrorResponse(
"400",
"There was an error with the request.",
"Required parameter '" + e.getParameterName() + "' is missing.");
}
...
I think the message is being written but not flushed...
So your converter may be missing something like:
outputMessage.getBody().flush();
Maybe even use Spring's AbstractHttpMessageConverter ?

Categories