Saturday, August 29, 2020

How to Log RestTemplate Request Response without destroying body

When we are working with REST services, it can be very useful for debugging to be able to log both the request and the response info. We can do this very easy just by adding an interceptor in our project.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import lombok.extern.log4j.Log4j2;

@Log4j2
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
		traceRequest(request, body);
		ClientHttpResponse response = execution.execute(request, body);
		traceResponse(response);
		return response;
	}

	private void traceRequest(HttpRequest request, byte[] body) throws IOException {
		log.info("=========================== Request Begin ===========================");
		log.info("URI          : " + request.getURI());
		log.info("Method       : " + request.getMethod());
		log.info("Headers      : " + request.getHeaders());
		log.info("Body : " + new String(body, "utf-8"));
		log.info("============================ Request End ============================");
	}

	private void traceResponse(ClientHttpResponse response) throws IOException {
		StringBuilder inputStringBuilder = new StringBuilder();
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "utf-8"));
		String line = bufferedReader.readLine();
		while (line != null) {
			inputStringBuilder.append(line);
			line = bufferedReader.readLine();
		}
		log.info("=========================== Response Begin ===========================");
		log.info("Status code   : {}", response.getStatusCode());
		log.info("Status text   : {}", response.getStatusText());
		log.info("Headers       : {}", response.getHeaders());
		log.info("Body          : {}", inputStringBuilder.toString());
		log.info("============================ Response End ============================");
	}

}

Now create a BeanConfig class to create RestTemplate object.

import java.util.Collections;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import com.altafjava.misc.LoggingRequestInterceptor;

@Configuration
public class BeanConfig {

	@Bean
	public RestTemplate buildrestTemplate() {
		RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
		restTemplate.setInterceptors(Collections.singletonList(new LoggingRequestInterceptor()));
		return restTemplate;
	}
}

That's it! But you will run in a problem.

How to Log RestTemplate Request Response without destroying body


Actually Response body is a stream and if we read it in the interceptor it won’t be available for RestTemplate to deserialize it into object model. In other words, when we call restTemplate.get..() we will always get back empty objects (even as we see the object in the response. Fortunately we can fix that by using org.springframework.http.client.BufferingClientHttpResponseWrapper class.

BufferingClientHttpResponseWrapper

But unfortunately, you cannot use this class because this class is not public. Hence we cannot create object of this class. Hence to solve this we need to manually create this class with public modifier. Hence to solve this finally lets follow some steps.


Step 1 :-

First we should create a BufferingClientHttpResponseWrapper class which will copy the response from actual response. Now with this copied response we can use response.getBody() any time.
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;

public class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

	private final ClientHttpResponse response;
	private byte[] body;

	public BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
		this.response = response;
	}

	@Override
	public InputStream getBody() throws IOException {
		if (body == null) {
			body = StreamUtils.copyToByteArray(response.getBody());
		}
		return new ByteArrayInputStream(body);
	}

	@Override
	public HttpStatus getStatusCode() throws IOException {
		return this.response.getStatusCode();
	}

	@Override
	public int getRawStatusCode() throws IOException {
		return this.response.getRawStatusCode();
	}

	@Override
	public String getStatusText() throws IOException {
		return this.response.getStatusText();
	}

	@Override
	public HttpHeaders getHeaders() {
		return this.response.getHeaders();
	}

	@Override
	public void close() {
		this.response.close();
	}
}


Step 2 :-

Create a custom interceptor class ie LoggingRequestInterceptor which will implement ClientHttpRequestInterceptor class. This class will be responsible to print all the RestTemplate request & response logs.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import lombok.extern.log4j.Log4j2;

@Log4j2
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
		traceRequest(request, body);
		ClientHttpResponse response = execution.execute(request, body);
		response = traceResponse(response);
		return response;
	}

	private void traceRequest(HttpRequest request, byte[] body) throws IOException {
		log.info("=========================== Request Begin ===========================");
		log.info("URI          : " + request.getURI());
		log.info("Method       : " + request.getMethod());
		log.info("Headers      : " + request.getHeaders());
		log.info("Body : " + new String(body, "utf-8"));
		log.info("============================ Request End ============================");
	}

	private ClientHttpResponse traceResponse(ClientHttpResponse response) throws IOException {
		ClientHttpResponse newCopiedResponse = new BufferingClientHttpResponseWrapper(response);
		StringBuilder inputStringBuilder = new StringBuilder();
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(newCopiedResponse.getBody(), "UTF-8"));
		String line = bufferedReader.readLine();
		while (line != null) {
			inputStringBuilder.append(line);
			line = bufferedReader.readLine();
		}
		log.info("=========================== Response Begin ===========================");
		log.info("Status code   : {}", response.getStatusCode());
		log.info("Status text   : {}", response.getStatusText());
		log.info("Headers       : {}", response.getHeaders());
		log.info("Response Body : {}", inputStringBuilder.toString());
		log.info("============================ Response End ============================");
		return newCopiedResponse;
	}
}

Step 3 :-

Create a Bean configuration class which will create RestTemplate object.
import java.util.Collections;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import com.altafjava.misc.LoggingRequestInterceptor;

@Configuration
public class BeanConfig {

	@Bean
	public RestTemplate buildrestTemplate() {
		RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
		restTemplate.setInterceptors(Collections.singletonList(new LoggingRequestInterceptor()));
		return restTemplate;
	}
}

Thats It!
Happy Coding.

For your reference you can the project in https://github.com/altafjava/log-resttemplate-req-resp

Thursday, August 13, 2020

java.lang.AssertionError: UnsupportedOperationException: Cannot define class using reflection

Problem:

If you are using EqualsVerifier for JUnit tesing purpose then there may be a chance you can get this error.

java.lang.AssertionError: UnsupportedOperationException: Cannot define class using reflection

equalsverifier



Solution:

To overcome this error we just have to use the upper version of equalsverifier dependency. Till the version 2.4.7 we will get this error. Hence we can use the equalsverifier 2.4.8 or higher than 2.4.8.

Example:-

<dependency>
    <groupId>nl.jqno.equalsverifier</groupId>
    <artifactId>equalsverifier</artifactId>
    <version>2.4.8</version>
    <scope>test</scope>
</dependency>

Thats it!

Happy Coding.

java.io.FileNotFoundException while using classLoader.getResource(fileName) method

Problem: 

I have a method loadFileFromClassPath(String fileName). Inside the method I am taking help of ClassLoader to get the File object and returning it.

    public static File loadFileFromClassPath(String fileName) {
        File file;
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        file = new File(classLoader.getResource(fileName).getFile());
        return file;
    }

There is an another method method fileDataToString(String fileName) which is using loadFileFromClassPath(String fileName) method.
    public static String fileDataToString(String fileName) throws IOException {
        final StringBuilder sb = new StringBuilder();
        final InputStream is = loadFileFromClassPath(fileName);
        String line;
        try(BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
        	while ((line = br.readLine()) != null) {
        		sb.append(line);
        	}
	}
        return sb.toString();
    }


But here classLoader.getResource(fileName) method is not working and causing java.io.FileNotFoundException.


FileNotFoundException



Solution:

To solve this problem you can use classLoader.getResourceAsStream(fileName) which returns InputStream.
    public static InputStream loadFileFromClassPath(String fileName) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        InputStream is=loader.getResourceAsStream(fileName);
        return is;
    }

And you just need to change some logic to read the data from InputStream to String in fileDataToString(String fileName) method as
    public static String fileDataToString(String fileName) throws IOException {
        final StringBuilder sb = new StringBuilder();
        final InputStream is = loadFileFromClassPath(fileName);
        String line;
        try(BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
        	while ((line = br.readLine()) != null) {
        		sb.append(line);
        	}
	}
        return sb.toString();
    }

Thats it!
Happy Coding.






The forked VM terminated without properly saying goodbye. VM crash or System.exit called

 At the time of `mvn clean install` if you are getting this error.


you just have add a plugin in your pom.xml file.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
    <forkCount>3</forkCount>
    <reuseForks>true</reuseForks>
    <argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
  </configuration>
</plugin>


Thats All!
Happy Coding.




Caused by: java.lang.ClassNotFoundException: com.sun.tools.javac.code.TypeTags when using lombok

  At the time of `mvn clean install` if you are getting this error then it that means you are at the right place.

com.sun.tools.javac.code.TypeTags


And this error is coming means you are using Lombok dependency in your project. See the Maven dependency in your project and check the Lombok version.


lombok-version


To solve this error you need to use lombok version higher that 1.16.20. Means at least minimum version you can use 1.16.22 or higher than it.

Example:-

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
<optional>true</optional>
</dependency>

Fatal error compiling: java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTags

 At the time of `mvn clean install` if you are getting this error then it that means you are at the right place.

com.sun.tools.javac.code.TypeTags


And this error is coming means you are using Lombok dependency in your project. See the Maven dependency in your project and check the Lombok version.


lombok-version


To solve this error you need to use lombok version higher that lombok-1.16.20. Means at least minimum version you can use lombok-1.16.22 or higher than it.