I wanted to write a Annotation based api which can record and request and response in my library.
Basically the plan was if a developer did something like this:
#RequestMapping(value = "/todo", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
#CustomCopy
public void createNewTodo() {
I would copy the request and response and dump to a database. I had started to use the HandlerInterceptorAdaptor with afterComplete but that did not lead anywhere since I cannot copy the response as it is flushed out by that time afterComplete is invoked.
Next I was planning to use the OncePerRequestFilter method. Basically use this as the template.
I just could not figure out:
How can I connect the Annotation with my filter ?
If there is another method where I can use the Annotation but also use the request and response body.
I was planning to use to look into AOP but still have not figured it out.
Thanks a lot for the help.
Using AOP you can do it like this:
dependency:
compile 'org.aspectj:aspectjweaver:{version}'
Create Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomCopy {
}
Save request response:
#Aspect
#Component
public class SaveRequestResponse {
#Around("#annotation(CustomCopy)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
//Request build
ContentCachingRequestWrapper request = getWrapper(joinPoint);
StringBuilder apiLog = new StringBuilder();
apiLog.append("Rest API: ").append(request.getRequestURL().toString()).append("\n");
apiLog.append("Body:").append(getRequestBody(request)).append("\n");
for (String header : Collections.list(request.getHeaderNames())) {
apiLog.append(header).append(":").append(request.getHeader(header))
.append("\n");
}
//Request build end
//method called
Object proceed = joinPoint.proceed();
//after method called response
String response = new ObjectMapper().writeValueAsString(proceed);
//save apiLog(Full request) && response
return proceed;
}
private String getRequestBody(final ContentCachingRequestWrapper wrapper) {
String payload = null;
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
try {
int maxLength = buf.length > 500 ? 500 : buf.length;
payload = new String(buf, 0, maxLength,
wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
logger.error("UnsupportedEncoding.", e);
}
}
}
return payload;
}
private ContentCachingRequestWrapper getWrapper(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
ContentCachingRequestWrapper request = null;
for (Object arg : args) {
if (arg instanceof ContentCachingRequestWrapper) {
request = (ContentCachingRequestWrapper) arg;
break;
}
}
return request;
}
}
Now use this #CustomCopy annotation in controller method.
Related
I'm trying for more than an hour to test this class. It went so ugly of stubbing the whole components of the method etc. I'd love some advice how to make a better test or refactor the class to make it way easier to test. I could not figure out a way yet.
Class to Test
#Slf4j
public final class HistoryRestService {
static RestTemplate restTemplate = new RestTemplate();
public static Optional<List<History>> findLatestHistories() {
String url = buildUrl();
ResponseEntity<History[]> responseEntity = null;
try {
responseEntity = restTemplate.getForEntity(url, History[].class);
} catch (ResourceAccessException e) {
log.warn("No connection to History persistence. Please check if the history persistence started up properly");
return Optional.empty();
}
History[] histories = responseEntity.getBody();
return Optional.of(Arrays.asList(histories));
}
private static String buildUrl() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("http://");
stringBuilder.append("localhost");
stringBuilder.append(":8081");
stringBuilder.append("/history/get");
return stringBuilder.toString();
}
// For Testing
static void setRestTemplate(RestTemplate restTemplate) {
HistoryRestService.restTemplate = restTemplate;
}
}
Spock Test which fails
class HistoryRestServiceTest extends Specification {
def "test findLatestHistories"() {
given:
History mockedHistory = Mock()
HistoryRestService uut = new HistoryRestService()
History[] expected = [mockedHistory]
RestTemplate mockedRestTemplate = Stub()
ResponseEntity<History> mockedResponseEntity = Stub()
mockedResponseEntity.getBody() >> expected
mockedRestTemplate.getForEntity(_) >> mockedResponseEntity
uut.setRestTemplate(mockedRestTemplate)
when:
def actual = uut.findLatestHistories()
then:
actual.get() == expected
}
}
I'd suggest using real depedency-injection (spring/guice/cdi) instead of static variables.
Furthermore, you should think about what you want to test, is it the correct request and parsing of the network call, then write an integration test using something like mockserver or wiremock to have the whole stack. Or, if you are just concerned with the result handling, then you could move the code that interacts with RestTemplate into a separate method and use partial mocking to mock this method. I'd suggest to use the real integration test, but for the sake of an example this should work, but I didn't verify the code.
#Slf4j
public class HistoryRestService {
private final RestTemplate restTemplate;
public HistoryRestService() {
restTemplate = new RestTemplate();
}
public HistoryRestService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Optional<List<History>> findLatestHistories() {
try {
return Optional.of(Arrays.asList(getLatestHistories(buildUrl())));
} catch (ResourceAccessException e) {
log.warn("No connection to History persistence. Please check if the history persistence started up properly");
return Optional.empty();
}
}
History[] getLatestHistories(String url) throws {
ResponseEntity<History[]> responseEntity = null;
responseEntity = restTemplate.getForEntity(url, History[].class);
return responseEntity.getBody()
}
private String buildUrl() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("http://");
stringBuilder.append("localhost");
stringBuilder.append(":8081");
stringBuilder.append("/history/get");
return stringBuilder.toString();
}
}
class HistoryRestServiceTest extends Specification {
#Subject
HistoryRestService uut = Spy()
def "test findLatestHistories"() {
given:
History[] expected = [mockedHistory]
when:
def actual = uut.findLatestHistories()
then:
actual.get() == expected
1 * uut.getLatestHistories(_ as String) >> expected
}
def "test findLatestHistories returns empty on exceptions"() {
given:
History[] expected = [mockedHistory]
when:
def actual = uut.findLatestHistories()
then:
!actual.present
1 * uut.getLatestHistories(_ as String) >> {throw new ResourceAccessException()}
}
}
I have implemented filter and I have called getEntityStream of ContainerRequestContext and set the exact value back by using setEntitystream. If i use this filter then #FormParameter data becomes null and if i don't use filter then everything will be fine (as I am not calling getEntityStream) and i have to use filter to capture request data.
Note: I am getting form params from MultivaluedMap formParams but not from #FormParameter.
Environment :- Rest Easy API with Jboss Wildfly 8 server.
#Provider
#Priority(Priorities.LOGGING)
public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter{
final static Logger log = Logger.getLogger(CustomLoggingFilter.class);
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
MDC.put("start-time", String.valueOf(System.currentTimeMillis()));
String entityParameter = readEntityStream(requestContext);
log.info("Entity Parameter :"+entityParameter);
}
private String readEntityStream(ContainerRequestContext requestContext){
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final InputStream inputStream = requestContext.getEntityStream();
final StringBuilder builder = new StringBuilder();
int read=0;
final byte[] data = new byte[4096];
try {
while ((read = inputStream.read(data)) != -1) {
outStream.write(data, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] requestEntity = outStream.toByteArray();
if (requestEntity.length == 0) {
builder.append("");
} else {
builder.append(new String(requestEntity));
}
requestContext.setEntityStream(new ByteArrayInputStream(requestEntity) );
return builder.toString();
}
return null;
}
}
class customResource
{
//// This code is not working
#POST
#Path("voiceCallBack")
#ApiOperation(value = "Voice call back from Twilio")
public void voiceCallback(#FormParam("param") String param)
{
log.info("param:" + param);
}
// This code is working
#POST
#Path("voiceCallBackMap")
#ApiOperation(value = "Voice call back from Twilio")
public void voiceCallbackMap(final MultivaluedMap<String, String> formParams)
{
String param = formParams.getFirst("param");
}
}
please suggest me solution & Thanks in Advance.
I found during run time that instance of the entity stream (from http request) is of type org.apache.catalina.connector.CoyoteInputStream (I am using jboss-as-7.1.1.Final). But we are setting entity stream with the instance of java.io.ByteArrayInputStream. So Resteasy is unable to bind individual formparmeters.
There are two solutions for this you can use any one of them :
Use this approach How to read JBoss Resteasy's servlet request twice while maintaing #FormParam binding?
Get form parameters like this:
#POST
#Path("voiceCallBackMap")
#ApiOperation(value = "Voice call back from Twilio")
public void voiceCallbackMap(final MultivaluedMap<String, String> formParams)
{
String param = formParams.getFirst("param");
}
In my app I am using netflix zuul to route a request from a microservice (gateway) to another. The requests are being routed fine but I also want to introduce some parameters in the request body before it is routed to the appropriate microservice. For this I am using Zuul pre filter like this.
public class SimpleFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
try {
RequestContext context = RequestContext.getCurrentContext();
InputStream in = (InputStream) context.get("requestEntity");
if (in == null) {
in = context.getRequest().getInputStream();
}
String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
// body = "request body modified via set('requestEntity'): "+ body;
body = body.toUpperCase();
context.set("requestEntity", new ByteArrayInputStream(body.getBytes("UTF-8")));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return null;
}
}
For now I am just trying to change the body to upper case but the microservice to which this request is routed doesn't receive the modified body (upper case). Instead it receives the original one. Am I doing something wrong. Any help would be appreciated. Thanks !!
Was able to do the following - transform a GET request to a POST request, and add body content to the (proxied) POST request.
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
context.addZuulRequestHeader("Content-Type", "application/x-www-form-urlencoded");
String body = String.format("a=%s&b=%s", a, b);
final byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
context.setRequest(new HttpServletRequestWrapper(context.getRequest()) {
#Override
public ServletInputStream getInputStream() {
return new ServletInputStreamWrapper(bytes);
}
#Override
public int getContentLength() {
return bytes.length;
}
#Override
public long getContentLengthLong() {
return bytes.length;
}
#Override
public String getMethod() {
return "POST";
}
});
return null;
}
try this one It's may be work in your case .
requestContext.getCurrentContext().put("requestEntity", new ByteArrayInputStream(body.getBytes("UTF-8")));
Turned out this method cannot change the request body within the requestContext. Truly in the requestContext, a new field "requestEntity" is added, however, the request body from context.getRequest().getInputStream() remains the same after this operation.
You can modify the request body, see this answer for an example. You just need to wrap the new request data and make sure you correctly report it's new content length.
Is there anyway to force spring to always produce json, even an empty json object if there's no data to return.
Our services go through another service that rejects any response that isn't valid json (regardless of status code). It's not nice but we have no control of this.
With spring controllers you can tell them to produce json, but this only works when there's content to return. Is there a quick and elegant way to make all responses be json?
#RequestMapping(value = "/test", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public
#ResponseBody
ResponseEntity<String> test(){
// if this returns null or an empty string the response body will be emtpy
// and the content-type header will not be set.
return service.getData();
}
The simply fix here is to simply add an if statement to check for null. But that's ugly as I'll have to manually set the header and the response body.
I'm hoping someone knows of a nicer way?
Thanks
If you want all responses to return application/json, then you can set this at a single place by overriding postHandle() from HandlerInterceptorAdapter:
#Component
public class ResponseInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws IOException {
if (response.getContentType() == null || response.getContentType().equals("")) {
response.setContentType("application/json");
}
}
}
You can look here
You may wrap the response in a "Container" object
For example I use this BaseAjaxResponse:
public class BaseAjaxResponse implements Serializable
{
private static final long serialVersionUID = 9087132709920851138L;
private int codiceOperazione;
private String descrizioneEsitoOperazione;
private long numeroTotaleOggetti;
private long numeroOggettiRestituiti;
private List<? extends Object> payload;
//Constructors and getter/setter
}
Then in my controllers I use this strategy:
#RequestMapping(method = { RequestMethod.POST }, value = { "/find" })
public ResponseEntity<BaseAjaxResponse> createCandidato(#RequestBody CandidatoDto candidato){
BaseAjaxResponse bar = new BaseAjaxResponse();
HttpStatus statusCode = null;
List<Object> payload = null;
StopWatch sw = new StopWatch("Find");
try
{
sw.start();
payload = myService.find();
sw.stop();
if( payload == null || payload.isEmpty() )
{
statusCode = HttpStatus.NO_CONTENT;
bar.setCodiceOperazione(statusCode.value());
bar.setDescrizioneEsitoOperazione("No result");
}
else
{
statusCode = HttpStatus.OK;
bar.setCodiceOperazione(statusCode.value());
bar.setDescrizioneEsitoOperazione("Got result");
//Set the object count and the number of found objects
}
}
catch (Exception e)
{
String message = "Errore nell'inserimento di un candidato; "+e.getMessage();
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
bar.setCodiceOperazione(statusCode.value());
bar.setDescrizioneEsitoOperazione(message);
logger.error(message, e);
}
finally
{
if( sw.isRunning() )
{
sw.stop();
if( logger.isDebugEnabled() )
{
logger.debug("CHIUSURA STOPWATCH FORZATA. "+sw.toString());
}
}
}
return new ResponseEntity<BaseAjaxResponse>(bar, statusCode);
}
I hope this can be useful
Angelo
In my Jax-Rs(Jersey + Jetty) application, I've added some custom headers to the response so that I can use those at the client side. I'm using ResponseBuilder to achieve this.
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionMapper.class);
#Context
private HttpHeaders headers;
#Override
public Response toResponse(CustomException exception) {
String errorKey = null;
String errorArgs[] = null;
// log stack trace
LOGGER.error("An error occurred during the operation.", exception);
if (exception instanceof MetadataServerException) {
errorKey = ExceptionMapperUtil.checkForDBException(exception.getCause());
if (errorKey != null) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(MessageSourceUtils.getMessage(errorKey, errorArgs, ExceptionMapperUtil.getLocale(headers))).type(MediaType.TEXT_PLAIN).build();
}
}
if(ErrorCodes.INTERNAL_SERVER_ERROR.equals(exception.getMessageKey()) &&
(exception.getMessageArguments() == null || exception.getMessageArguments().length==0) ){
exception.setMessageArguments(new Object[]{"while executing the operation"});
}
// prepare response to be sent to client
return Response.status(ResponseCodeMapper.mapToStatusCode(exception)).
entity(exception.getMessage(ExceptionMapperUtil.getLocale(headers))).
type(MediaType.TEXT_PLAIN).header("errorCode", exception.getMessageKey()).
build();
}
}
And also, I've confirmed that that the response object is getting built properly. However, for some reason, the returned response doesn't contain my custom header. Any clues as to to what is going wrong ?