Apache CXF ExceptionMapper with request message - java

I was wondering if something like this is possible or if there's a better way to do it:
public class WebServiceExceptionMapper implements ExceptionMapper<Exception> {
private static Logger logger = LoggerFactory.getLogger(WebServiceExceptionMapper.class);
#Override
public Response toResponse(Exception ex, Message requestMessage) {
logger.error("request=" + httpMessage.etc..etc.., ex);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
Basically I would like to be able to log not just the exception but the request message that triggered the error. More specifically if the request contains a JSON or XML I would like to grab that.

NVM. Figured it out by injecting MessageContext in my ExceptionMapper and adding a custom RequestHandler like described here: http://cxf.547215.n5.nabble.com/Accessing-JAXRS-JSON-content-directly-td4390185.html
public class StringContentRequestHandler implements RequestHandler {
private static final Logger logger = LoggerFactory.getLogger(RawContentRequestHandler.class);
public Response handleRequest(Message m, ClassResourceInfo resourceClass) {
InputStream is = m.getContent(InputStream.class);
try {
CachedOutputStream bos = new CachedOutputStream();
IOUtils.copy(is, bos);
bos.flush();
is.close();
m.setContent(InputStream.class, bos.getInputStream());
StringBuilder builder = new StringBuilder();
bos.writeCacheTo(builder, "utf-8");
m.setContent(String.class, builder.toString());
return null;
} catch (IOException ex) {
logger.error("IOException on getting raw content", ex);
return null;
}
}
}

Related

How to read payload in Jersey Client

I have a little problem here. When firing a request I want to sign the whole message with HMAC and add the resulting signature to the headers.
So I implemented
javax.ws.rs.ext.WriterInterceptorContext
In the
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException
method I cannot access the string representation of the entity. It always returns an empty String. The cause seems to be the MessageBodyWriter which is executed AFTER the WriterInterceptor.
Basically I have the following two scenarios failing:
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
try {
final ClientOutputStream stream = (ClientOutputStream) requestContext.getProperty(HTTPCLIENT_ENTITY_STREAM);
String payload = stream.getString(Charset.forName("UTF-8")); // returns alway empty String
String signature = doSomeSuffWithPayload(payload);
MultivaluedMap<String, Object> headers = context.getHeaders();
headers.add(HmacHeaderValue.X_SIGNATURE.headerName(), signature);
context.proceed();
} catch (IllegalArgumentException | ParseException | InvalidKeyException | NoSuchAlgorithmException ex) {
LOGGER.error(ex.getMessage());
} catch (UnsupportedEncodingException ex) {
LOGGER.error(ex.getMessage());
} catch (IOException ex) {
LOGGER.error(ex.getMessage());
}
}
Here the doSomeSuffWithPayload(payload) method does not work, because payload is always empty.
I thought a trick will do it, so I switched the context.proceed() call to anyother place:
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
try {
context.proceed();
final ClientOutputStream stream = (ClientOutputStream) requestContext.getProperty(HTTPCLIENT_ENTITY_STREAM);
String payload = stream.getString(Charset.forName("UTF-8")); // returns the right string representation
String signature = doSomeSuffWithPayload(payload);
MultivaluedMap<String, Object> headers = context.getHeaders();
headers.add(HmacHeaderValue.X_SIGNATURE.headerName(), signature); // doesn't add the header
} catch (IllegalArgumentException | ParseException | InvalidKeyException | NoSuchAlgorithmException ex) {
LOGGER.error(ex.getMessage());
} catch (UnsupportedEncodingException ex) {
LOGGER.error(ex.getMessage());
} catch (IOException ex) {
LOGGER.error(ex.getMessage());
}
}
In this case the string representation of the entity is ok. But adding the header to the request does not work.
So atm I can either have the (wrong) signature added to the headers and an always empty entity OR the right signature with the correct entity, but the header is not added.
My question is: Does anybody know a way to get the string representation of the entity by using the WriterInterceptor?
EDITH said:
We are using version 2.25.1 of jersey client. 2.27 didn't solve the problem either.
After searching deep in the API I found out that the entity gets indeed written after the WriterInterceptor in the MessageBodyWriter. Beside that the headers also get added during the process in the MessageBodyWriter. That's why both approaches above don't work.
My solution atm is to geht the right MessageBodyWriter and let it serialize the entity as it would do in the MessageBodyWriter that is executed after the WriterInterceptor. It this case the WriterInterceptor is not needed anymore, implementing ClientRequestFilter will do the trick.
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
#Context
private Providers providers;
private String getPayloadFromRequest(ClientRequestContext requestContext) throws IOException {
Object object = requestContext.getEntity();
if (object != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// buffer into which myBean will be serialized
Class<Object> type = (Class<Object>) requestContext
.getEntityClass();
GenericType<Object> genericType = new GenericType<Object>(type) {
};
// get most appropriate MBW
final MessageBodyWriter<Object> messageBodyWriter = providers
.getMessageBodyWriter(type, type, new Annotation[]{},
MediaType.APPLICATION_JSON_TYPE);
try {
// use the MBW to serialize myBean into baos
messageBodyWriter.writeTo(object, object.getClass(),
genericType.getType(), new Annotation[]{},
MediaType.APPLICATION_JSON_TYPE,
new MultivaluedHashMap<String, Object>(), baos);
} catch (IOException e) {
throw new RuntimeException(
"Error while serializing MyBean.", e);
}
return baos.toString();
} finally {
baos.close();
}
} else {
return "";
}
}
The code is not mine, but unfortunately I lost the source.

How to produce and consume rest service using jersey via application/octet_stream?

Firstly, I need to produce rest service for sending a POJO class include byte array field for image and other POJO class. Also need to consume this service using jersey client.It is possible to achive these using application/octet-stream MediaType. I already did it for only image file and it is working.
What is the correct way to do this?
public class Sample{
int sampleId;
Byte[] image;
Foo foo;
//constructor
//getter setter
}
public class GetDataImage {
#GET
#Path("/gets")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getFile(#QueryParam("id") String id ) throws IOException {
File file = new
File("..\test_image.jpg");
RenderedImage image2 = ImageIO.read(file);
Foo foo = new Foo();
Sample sample = new Sample (1, new Byte[] {},foo );
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectMapper mapper = new ObjectMapper(new BsonFactory());
mapper.writeValue(baos, responseChipoutImage);
StreamingOutput stream = new StreamingOutput() {
#Override
public void write(OutputStream output) throws IOException {
try {
// ImageIO.write(image2, "jpg", output);
new ObjectOutputStream(output).writeObject(responseChipoutImage);
} catch (Exception e) {
e.printStackTrace();
}
}
};
return Response.ok(stream, "application/octet-stream")
.header("content-disposition", "attachment; filename = " + image2.toString())
.build();
}
}
This is the client:
public class Client {
private static final String BASE_URI = "http://localhost:8090/Test/gets";
public Client () throws IOException {
try {
Client client = Client.create();
WebResource objWebResource = client.resource(BASE_URI);
ClientResponse response = objWebResource.path("/").queryParam("id", "1")
.type(javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM).get(ClientResponse.class);
System.out.println("response : " + response);
if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
ResponseSample responseSample = response.getEntity(ResponseSample.class);
// InputStream input = (InputStream)response.getEntity(InputStream.class);
// BufferedImage bf = ImageIO.read(input);
// File outputfile = new File("../test.jpeg");
// ImageIO.write(bf, "jpg", outputfile);
ObjectMapper mapper = new ObjectMapper(new BsonFactory());
// deserialize data
}
} catch (UniformInterfaceException e) {
e.printStackTrace();
} catch (ClientHandlerException e) {
e.printStackTrace();
}
}
public static void main(String... args) throws IOException {
new Client();
}
I found the solution finally. The solution is hidden in Jackson JSON parser --> Bson4jackson.
changed server side StreamingOutput ovveride method like this :
StreamingOutput stream = new StreamingOutput() {
#Override
public void write(OutputStream output) throws IOException {
try {
ObjectMapper mapper = new ObjectMapper(new BsonFactory());
mapper.writeValue(output, responseChipoutImage);
} catch (Exception e) {
e.printStackTrace();
}
}
};
and then catched the data from client adding jackson bson parser from InputStream.
public class Client {
private static final String BASE_URI = "http://localhost:8090/Test/gets";
public Client() throws IOException {
try {
Client client = Client.create();
WebResource objWebResource = client.resource(BASE_URI);
ClientResponse response = objWebResource.path("/").queryParam("id", "1")
.type(javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM).get(ClientResponse.class);
if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
ObjectMapper mapper = new ObjectMapper(new BsonFactory()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ResponseSample responseSample = mapper.readValue(response.getEntityInputStream(), ResponseSample.class);
}
} catch (UniformInterfaceException e) {
e.printStackTrace();
} catch (ClientHandlerException e) {
e.printStackTrace();
}
}
public static void main(String...args) throws IOException {
new Client();
}

How to rewrite POST request body on HttpServletRequest

I'm working on a Filter in which I have to get the request payload, decrypt it, check if it's a valid JSON and if it is go on with the chain and go to my service. The thing is that, so far I haven't been able to find a way to rewrite the body. Why I want to rewrite it? As the service expects a JSON and the request has an encrypted text in the body, once I decrypt it I want the body to be the decrypted JSON. Also, once I return from the service, I should rewrite the response to have the json encrypted. I've read a lot of forums and questions but couldn't get to a working solution.
Here's my code:
RequestLoginFilter.java
#WebFilter("/RequestLoginFilter")
public class RequestLoginFilter implements Filter{
protected final static Log logger = LogFactory.getLog(RequestLoginFilter.class);
private ServletContext context;
private CryptoUtil crypto;
public void init(FilterConfig fConfig) throws ServletException {
this.context = fConfig.getServletContext();
this.context.log("RequestLoggingFilter initialized");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// use wrapper to read multiple times the content
AuthenticationRequestWrapper req = new AuthenticationRequestWrapper((HttpServletRequest) request);
HttpServletResponse resp = (HttpServletResponse) response;
String payload = req.getPayload();
try {
String decryptedPayload = crypto.decrypt(payload);
JSONUtils.convertJSONStringToObject(decryptedPayload, LoginTokenTO.class);
} catch (GeneralSecurityException e) {
logger.error("Error when trying to decrypt payload '"+payload+"'");
throw new ServletException("Error when trying to decrypt payload '"+payload+"'", e);
}
chain.doFilter(req, resp);
System.out.println("a ver");
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
And also the wrapper, just in case:
AuthenticationRequestWrapper.java
public class AuthenticationRequestWrapper extends HttpServletRequestWrapper {
protected final static Log logger = LogFactory.getLog(AuthenticationRequestWrapper.class);
private final String payload;
public AuthenticationRequestWrapper (HttpServletRequest request) throws AuthenticationException {
super(request);
// read the original payload into the payload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
logger.error("Error reading the request payload", ex);
throw new AuthenticationException("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
// ignore
}
}
}
payload = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream () throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(payload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
public String getPayload() {
return payload;
}
}
Hopefully somebody here knows how I can get to get this working.
Thanks in advance guys.
Whilst what you are asking is probably technically possible, it doesn't sound like the right approach to me.
What you need is a security layer that sits between the incoming request (endpoint) and your service. Re-writing the body of the request is a strange thing to be doing (which probably explains why you're having issues). Is there a reason you want this to be done in a Filter? After all, filters are designed to filter requests, not rewrite them ;)
A more logical/transparent solution would be to have your endpoint accept all incoming requests, decrypt and validate them before passing the request onto your service tier. Something like this:
public void handleRequest(Request request) {
try {
IncomingRequest x = securityManager.decrypt(request);
Response r = myService.handleRequest(x);
handleResponse(securityManager.encrypt(r));
}catch(InvlidateMessage x) {
handleInvalidMessage...
}catch(BusinessException x) {
handleBusinessException...
}
}

How to retrieve a mapped exception from jersey?

I've used an ExceptionMapper on the server side, putting the custom exception in the Response's body. How can I retrieve the original exception on the client side, and throw it to the caller?
You can serialize the exception and include it as a part of the response:
public final class SerializingExceptionMapper implements ExceptionMapper<Exception> {
#Override
public Response toResponse(Exception exception) {
try {
final byte[] serializedException = serializeException(exception);
final String base64EncodedException = Base64.getEncoder().encodeToString(serializedException);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new Message(base64EncodedException))
.build();
} catch (Exception ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
private byte[] serializeException(Exception ex) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(ex);
oos.close();
return bos.toByteArray();
}
}
final class Message {
public Message(String exception) {
this.exception = exception;
}
public String exception;
}
Then on the client side you should do just the opposite:
Unmarshal the (maybe JSON) response
Decode the base64 encoded exception to a byte[]
De-serialize the exception
Create a ByteArrayInputStream
Create ObjectInputStream
Just readObject() the exception
Do whatever you want with it on the client side
PS: This can be achieved without any buffering (i.e. without the byte[]s) -> just use a StreamingOutput as .entity() and write to the provided output stream instead of a ByteArrayOutputStream. The same applies for deserialization on the client side.

Constructing a DataSource from an InputStream or Byte array

I am writing a small file upload utility thing as part of a larger project. Originally I was handling this from a servlet using the Apache commons File utility classes. Here is a snippet from a quick test client I wrote for the service:
public static void main(String[] args) {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.getInInterceptors().add(new LoggingInInterceptor());
factory.getOutInterceptors().add(new LoggingOutInterceptor());
factory.setServiceClass(FileUploadService.class);
factory.setAddress("http://localhost:8080/FileUploadService/FileUploadService");
FileUploadService client = (FileUploadService) factory.create();
FileType file = new FileType();
file.setName("statemo_1256144312279");
file.setType("xls");
DataSource source = new FileDataSource(new File("c:/development/statemo_1256144312279.xls"));
file.setHandler(new DataHandler(source));
Boolean ret = client.uploadFile(file);
System.out.println (ret);
System.exit(0);
}
This works absolutely fine. Now the problem comes when I am trying to replace the Apache commons utilities. In the above code I am creating a DataSource from a File with an absolute path name. In my servlet, I can't get an absolute path name however and the file I am sending over the wire is empty.
Here is the servlet code:
#SuppressWarnings("unchecked")
protected void doPost (final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
// form should have enctype="multipart/form-data" as an attribute
if (!ServletFileUpload.isMultipartContent (request)) {
LOG.info("Invalid form attribute");
return;
}
//DataInputStream in = new DataInputStream(request.getInputStream());
final DiskFileItemFactory factory = new DiskFileItemFactory ();
factory.setSizeThreshold(FILE_THRESHOLD_SIZE);
final ServletFileUpload sfu = new ServletFileUpload (factory);
sfu.setSizeMax(MAX_FILE_SIZE);
final HttpSession session = request.getSession();
final List<FileItem> files = new ArrayList<FileItem>();
final List<String> filesToProcess = new ArrayList<String>();
try {
final List<FileItem> items = sfu.parseRequest(request);
for (final FileItem f : items) {
if (!f.isFormField())
files.add(f);
}
/*for (final FileItem f : files) {
final String absoluteFileName = UPLOAD_DESTINATION + FilenameUtils.getName(f.getName());
//f.write(new File (absoluteFileName));
filesToProcess.add(absoluteFileName);
}*/
FileItem f = files.get(0);
LOG.info("File: " + FilenameUtils.getName(f.getName()));
LOG.info("FileBaseName: " + FilenameUtils.getBaseName(f.getName()));
LOG.info("FileExtension: " + FilenameUtils.getExtension(f.getName()));
FileUploadServiceClient client = new FileUploadServiceClient();
DataSource source = new FileDataSource(new File(f.getName()));
FileType file = new FileType();
file.setHandler(new DataHandler(source));
file.setName(FilenameUtils.getBaseName(f.getName()));
file.setType(FilenameUtils.getExtension(f.getName()));
Boolean ret = client.uploadFile(file);
LOG.info("File uploaded - " + ret);
filesToProcess.add(UPLOAD_DESTINATION + FilenameUtils.getName(f.getName()));
session.setAttribute("filesToProcess", filesToProcess);
final RequestDispatcher dispatcher = request.getRequestDispatcher("Validate");
if (null != dispatcher) {
dispatcher.forward(request, response);
}
} catch (FileUploadException e) {
LOG.info("Exception " + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
LOG.info("Exception " + e.getMessage());
e.printStackTrace();
}
}
I've been working on this for the better part of this morning and am not getting anywhere. Even if I get rid of the Apache commons file stuff completely and handle the parsing of the request myself, I still can't construct the DataSource appropriately.
Thanks!
This was rather simple actually, I just copied over the bytes from the InputStream to the DataSource:
FileItem f = files.get(0);
// there is a problem here where the file being created is empty, since we only have a
// partial path:
DataSource source = new FileDataSource(new File(f.getName()));
// because of the above problem, we are going to copy over the data ourselves:
byte[] sourceBytes = f.get();
OutputStream sourceOS = source.getOutputStream();
sourceOS.write(sourceBytes);
This is the code of commons-email ByteArrayDataSource
it sounds odd to try to replace apache commons - don't, unless you have a really good reason
you can get absolute paths in a servlet. You can call getServletContext().getRealPath("/") which will return the absolute path of your application, and then you can get files relative to it.
In our application there are objects that have properties InputStream and Name. We are using next class to construct DataSource with those properties.
public class InputStreamDataSource implements DataSource {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final String name;
public InputStreamDataSource(InputStream inputStream, String name) {
this.name = name;
try {
int nRead;
byte[] data = new byte[16384];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
inputStream.close();
buffer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public String getContentType() {
return new MimetypesFileTypeMap().getContentType(name);
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(buffer.toByteArray());
}
#Override
public String getName() {
return name;
}
#Override
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
}
Most of the solutions shown here require that the InpustStream be closed (read into memory). It is possible to wrap the InputStream in a DataSource object without closing the InputStream though:
private record PipedDataSource(InputStream in, String contentType, String encoding)
implements DataSource, EncodingAware {
public String getContentType() {
return contentType;
}
public InputStream getInputStream() {
return in;
}
public String getName() {
return "PipedDataSource";
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("No OutputStream");
}
#Override
public String getEncoding() {
return encoding;
}
}
The example above also implements EncodingAware. This can prevent the InputStream from being closed by third part libraries (for example java.mail.internet.MimeUtility) when they get the data source encoding.

Categories

Resources