I'm a Jersey newbie and I need to log the JSON response. I wish to take the entity and convert it to JSON exactly as done by the Jersey framework does (same mapper, etc.). Is there a way to extract its mapper (and call, for example, its writeValueAsString)?
You don't specify which package you use for producing the JSON response (neither you explain much about your jersey server), but I will assume you use Jackson.
You have some tracing in Jersey, take a look here, but as fas as I know it does not do what you need.
But first of all you should implement a LoggingFilter,
public class YourLoggingFilter implements ContainerResponseFilter {
#Override
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
throws IOException {
...
}
}
And I assume you extend the ResourceConfig class to add resources, you need to add the new filter here:
public class YourApplication extends ResourceConfig {
public YourApplication() {
super();
register(YourAuthenticationRequestFilter.class, Priorities.AUTHENTICATION);
register(YourExceptionMapper.class);
register(YourLoggingFilter.class);
packages("...");
}
}
And finally, a simple way to log your response inside YourLoggingFilter would be (here I assume Jackson, but this is just an example, I don't know what logger you are using, but don't open a file every time you do a request!!!)
Object obj = responseContext.getEntity();
ObjectMapper om = new ObjectMapper();
File file = new File("...");
try {
OutputStream out = new FileOutputStream(file);
om.writeValue(out, obj);
} catch (IOException e) {
// this could fail but you don't want your request to fail
e.printStackTrace();
}
Hope it helps.
Related
Using latest Spring Boot as of May 2018. I've created a 404 response like this.
#ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
private final int errorId;
public NotFoundException(String errorMsg) {
super("-1," + errorMsg);
this.errorId = -1;
}
public NotFoundException(int errorId, String errorMsg) {
super(errorId + "," + errorMsg);
this.errorId = errorId;
}
public int getErrorId() {
return errorId;
}
}
The annotation #ResponseStatus(HttpStatus.NOT_FOUND) makes my NotFoundException appear like a 404 reponse like this
{
"timestamp":1527751944754,
"status":404,
"error":"Not Found",
"exception":"com.myapp.exception.NotFoundException",
"message":"1000,Could not find data for owner: 1234","path":"/resource/owner/1234"
}
I hoped that property "getErrorId" would appear in the response automatically, like this
{
"timestamp":1527751944754,
"status":404,
"error":"Not Found",
"exception":"com.myapp.exception.NotFoundException",
"message":"Could not find data for owner: 1234","path":"/resource/owner/1234",
"errorId": 1000
}
Is the a simply way (like an annotiation to the getErrorId method) of having the property "errorId" in the response?
You use #ControllerAdvice and #ExceptionHanlder in Spring. that is exception controller. In fact, you will make custom exception controller and define exception.
This is sample code for you :
#ControllerAdvice("your.package")
public class CommonExceptionHandler {
#ExceptionHandler(value = NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public #ResponseBody ResponseEntity<?> setNotFoundException(Exception exception) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
// this is sample map. you will make your custom model and you use exception parameter.
Map<String, String> map = new HashMap<String, String>();
map.put("timestamp", String.valueOf(new Date().getTime()));
map.put("status", HttpStatus.NOT_FOUND.toString());
map.put("error", "Not Found");
map.put("exception", exception.getMessage());
map.put("message", "Could not find data for owner: 1234");
map.put("path", "/resource/owner/1234");
map.put("errorId", "1000");
String json = mapper.writeValueAsString(map);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(json);
}
}
what ever Byeon0gam told everything is fine, here i am going to show another way means little bit of difference in maintaining code.
We know already ,
we can handle exceptions in spring-rest by 4 ways:
1. Using ResponseEntity Class.
2. Using #ResponseStatus Annotation.
3. Using #ExceptionHandler() Annotation.
4. Return Error Representation instead of default HTML error Page.
By using Those we can handle Exceptions at Method or Class level only.
But, if you want to handle Globally means throughout application , please follow below steps.
Handling Global Exception:
To Handle all Exceptions in our applications ,
First we need to create a class, after we need to use #ControllerAdvice Annotation on top of a class. In that class body , we can handle the exceptions raised in our application.
In that Class , we will create Exception handling methods , on top of every method we will use #ExceptionHandler() annotation for navigating Exceptions and for Handling .
If any exception raises in our application , based on #ExceptionHandler(“argument”) annotation argument the exception hadling method will be invoked and remaining handling code will be excuted.
#ControllerAdvice
public class SpringRestGlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<?> exceptionHandler(HttpServletRequest req, Exception e)
{
JSONObject obj =new JSONObject();
obj.put("msgTxt","Unknown Server Error, Please Contact Admin." );
obj.put("reqUrl", req.getRequestURI());
obj.put("stackTrace", e.toString());
obj.put("isErrorFlag", true);
obj.put("httpStatusCode", HttpStatus.OK.value());
gstcDaoi.saveExceptionOrErrorLog(prepareTGstcExceptionOrErrorLogObject(obj));
e.printStackTrace();
return new ResponseEntity<>(obj, HttpStatus.OK);
}
I have some JAX-RS web services that takes JSON input from clients, and return JSON outputs to clients. I need to log both the input and output JSON messages. I know how to do so for inputs, as shown in code below. This code will grab the exact JSON input that is coming in. How do I do the same for the outputs?
public class LogRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
InputStream inputStream = requestContext.getEntityStream();
// Read the JSON request input from the input stream.
}
}
Use ContainerResponseFilter to get the exchanged Entity. Add the filter to the provider list of the JAX-RS server
public class LogResponseFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext inContext, ContainerResponseContext outContext) throws IOException{
Object entity = outContext.getEntity();
//log entity.toSgring()
//You can use the output stream to write a custom message
//OutputStream outputStream = outContext.getEntityStream();
}
}
To just log the payload, looking at this link https://stackoverflow.com/a/25337892/6371459 I see
The OutputStream is empty at the time the Filter is called because the JAX-RS runtime has not written to it. After your Filter the runtime will choose the correct MessageBodyWriter which will serialize the entity to the OutputStream
So it is needed to add a WriterInterceptor to be execuded after MessageBodyWriters write to desired content-type.
In the previous link you have the full code.
I'm using Jetty to implement JSR-356 Web socket for chat application.
For managed conversations and sending different message types I'm using DTO object which are serialized from/to json using Jackson and TextDecoder
public class JsonDtoCodec implements Decoder.Text<Dto>,Encoder.Text<Dto>{
ObjectMapper om = ... //some object mapper
#Override
public Dto decode(String json) throws DecodeException {
Dto dto = null;
try {
dto = om.readValue(json,Dto.class);
} catch (IOException e) {
//ToDo add Log
}
return dto;
}
#Override
public String encode(Dto dto) throws EncodeException {
try {
return om.writeValueAsString(dto);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Now, In my endpoint I'm using direct Dto objects:
#ServerEndpoint(value = "/{version}/{userType}/{token}",
decoders =JsonDtoCodec.class,
encoders = JsonDtoCodec.class)
public class ChatServerEndPoint {
#OnMessage
public void onMessage(final #PathParam("userType") String userType,
final #PathParam("token") String token,
final #PathParam("version") String version,
final Session session,
Dto dto)
throws IOException {
//handling all DTO related object and sending response
Dto response = .... // create some DTO
session.getAsyncRemote().sendObject(dto);
}
}
Unfortunately, I need to handle uploading image files through web socket up to 100Kb size.
Can I use the same endpoint for handling both, DTOs and Binary Data?
If not should I add new EndPoint and should I handle two separate connections one for binaries and one for Json content?
You most certainly can.
You would have 2 #OnMessage annotated methods. (one handling TEXT, the other BINARY)
Assuming that your JsonToDtoCodec extends Decoder.Text<Dto>, then your other method would be declared as ...
#OnMessage(maxMessageSize=100000)
public void onFileUpload(final Session session, final ByteBuffer buf)
{
}
Since you said the files would be a maximum of 100kb, then having the method pass in the complete ByteBuffer is sufficient. If it were larger, you could use an InputStream approach instead. As for limiting the size of that buffer, your maxMessageSize annotation would keep that sane.
Note: if a client attempts to send something over 100kb in size on binary, then the connection will be closed with close code 1009: Too Big.
I'm using RESTEasy 2.2.1.GA as my JAX-RS implementation to create a client to connect to a third party service provider. (Education.com's REST API if it matters)
To make sure I haven't missed an important implementation detail here are code samples:
Service Interface
#Path("/")
public interface SchoolSearch {
#GET
#Produces({MediaType.APPLICATION_XML})
Collection<SchoolType> getSchoolsByZipCode(#QueryParam("postalcode") int postalCode);
}
Calling Class
public class SimpleSchoolSearch {
public static final String SITE_URL = "http://api.education.com/service/service.php?f=schoolSearch&key=****&sn=sf&v=4";
SchoolSearch service = ProxyFactory.create(SchoolSearch.class, SITE_URL);
public Collection<SchoolType> getSchools() throws Exception {
Collection<SchoolType> schools = new ArrayList<SchoolType>();
Collection<SchoolType> response = service.getSchoolsByZipCode(35803);
schools.addAll(response);
return schools;
}
}
After setting up tests to make this call, I execute and see the following exception being thrown.
org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException: Unable to find JAXBContext for media type: text/html;charset="UTF-8"
From reading the RESTEasy/JAX-RS documentation, as I understand it, when the response is returned to the client, prior to the unmarshaling of the data, a determination is made (Content Negotiation??) about which mechanism to use for unmarshalling. (I think we're talking about a MessageBodyReader here but I'm unsure.) From looking at the body of the response, I see that what is returned is properly formatted XML, but the content negotiation (via HTTP header content-type is indeed text/html;charset ="UTF-8") is not allowing the text to be parsed by JAXB.
I think that the implementation is behaving correctly, and it is the service that is in error, however, I don't control the service, but would still like to consume it.
So that being said:
Am I correct in my understanding of why the exception is thrown?
How do I work around it?
Is there a simple one line annotation that can force JAXB to unmarshal the data, or will I need to implement a custom MessageBodyReader? (If that is even the correct class to implement).
Thanks!
Follow Up:
I just wanted to post the few changes I made to Eiden's answer. I created a ClientExecutionInterceptor using his code and the information available at Resteasy ClientExecutionInterceptor documentation. My final class looks like
#Provider
#ClientInterceptor
public class SimpleInterceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse response = ctx.proceed();
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
The big difference is the addition of the #Provider and #ClientExecutionInterceptor annotations. This should insure that the interceptor is properly registered.
Also, just for completeness, I registered the Interceptor slightly differently for my tests. I used:
providerFactory.registerProvider(SimpleInterceptor.class);
I'm sure there are several solutions to this problem, but I can only think of one.
Try so set the content-type using a ClientExecutionInterceptor:
public class Interceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse<?> execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse<?> response = ctx.proceed();
response
.getHeaders()
.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
public void getSchools() throws Exception {
ResteasyProviderFactory.getInstance()
.getClientExecutionInterceptorRegistry()
.register( new Interceptor() );
SchoolSearch service =
ProxyFactory.create(SchoolSearch.class, SITE_URL);
}
I dont know about any such annotation, others might do, but a workaround is to create a local proxy. Create a controller, that passes all parameters to education.com using a
java.Net.URL.get()
return the answer that you received, but modify the header. Then connect your client to the local proxy controller.
When I try using standard servlet approach, in my browser the popup window shows up asking me whether to open .xls file or save it.
I tried the exactly same code via JAX-RS and the browser popup won't show up somehow. Has anyone encounter this?
JAX-RS way that won't display popup:
#Path("excellaTest")
public class ExcellaTestResource {
#Context
private UriInfo context;
#Context
private HttpServletResponse response;
#Context
private HttpServletRequest request;
public ExcellaTestResource() {
}
#Path("horizontalProcess")
#GET
//#Produces("application/vnd.ms-excel")
#Produces("application/vnd.ms-excel")
public void getProcessHorizontally() {
try {
URL templateFileUrl = this.getClass().getResource("myExcelTemplate.xls");
String templateFilePath = URLDecoder.decode(templateFileUrl.getPath(), "UTF-8");
String outputFileDir = "MasatoExcelHorizontalOutput";
ReportProcessor reportProcessor = new ReportProcessor();
ReportBook outputBook = new ReportBook(templateFilePath, outputFileDir, ExcelExporter.FORMAT_TYPE);
ReportSheet outputSheet = new ReportSheet("myExcelSheet");
outputBook.addReportSheet(outputSheet);
reportProcessor.addReportBookExporter(new OutputStreamExporter(response));
reportProcessor.process(outputBook);
System.out.println("done!!");
}
catch(Exception e) {
System.out.println(e);
}
return;
}
}//end class
class OutputStreamExporter extends ReportBookExporter {
private HttpServletResponse response;
public OutputStreamExporter(HttpServletResponse response) {
this.response = response;
}
//ReportProcessor output()
//This method is call when ReportProcessor process() is invoked.
//The Workbook from POI API can be used to write to stream
#Override
public void output(Workbook book, BookData bookdata, ConvertConfiguration configuration) throws ExportException {
//TODO write to stream
try {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=masatoExample.xls");
book.write(response.getOutputStream());
response.getOutputStream().close();
System.out.println("booya!!");
}
catch(Exception e) {
System.out.println(e);
}
}
}//end class
What JAX-RS framework are you using?
My guess is that your code doesn't work, because you are returning void. The framework you are using probably recognizes void as HTTP 204 No Content. This causes browser to skip the actual response body and to ignore conntent-disposition header.
As I already wrote you in a parallel thread: try to returning Response object. You can put either OutputStream or byte[] as entity as set the content-disposition header.
I have never used class level injection for a jax-rs service. I suggest one of 2 solutions.
1) Try injecting the request and response as method arguments.
2) Output your file to a byte array output stream and return a byte array from your method instead of void.