Instantiate IWebContext inside JmsListener in Spring - spring

I have a Spring app running as a rest api.
Let's assume that at some point, a message with some info is generated and stored in a AWS SQS queue.
When JMSListener is called, Im trying to generate a pdf report with thymeleaf and openhtmltopdf. I'm having troubles while instantiating IWebContext because it needs HttpServletRequest, HttpServletResponse, and Locale as params. Locale is not a problem as I could include it as a part of the SQS message, but I'm stuck with REQ and RES.
Code i'm using:
IWebContext ctx = new WebContext(¿REQUEST?, ¿RESPONSE?, servletContext, locale, mapParams);
String processedHtml = templateEngine.process(template, ctx);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useSVGDrawer(new BatikSVGDrawer());
builder.useFastMode();
builder.withHtmlContent(processedHtml, baseUrl);
builder.toStream(bos);
builder.run();
return bos.toByteArray();
} catch (Exception e) {
logger.error("xxx");
}
As it is being called inside #JmsListener(destination = "${aws.sqs.queue.name}") annotated method, I cannot use none of the following options:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
Because:
RequestContextHolder.getRequestAttributes()
is always null.
Thanks and regards.

I don't think you should be using an IWebContext for this. Instead, just use org.thymeleaf.context.Context.

Related

MockMvc Test does not get to the endpoint for a Multipart file in a RestController

I am calling a service in an orders controller which receives a multipart file and processes it and saving it into a database. I am trying to create a Spring Rest Doc for it but it is not even hitting the endpoint. I am creating a list of orders which is what the service expects. It receives the order as a stream as shown and converts into a stream of orders before saving it into a database. I have shown the main part of the controller and my code for generating the rest docs. When I run the code I get the following exception, it never even hits the endpoint when I set a breakpoint. I also used fileupload() but that did not work either.
Exception is:
Content type = application/json
Body = {"path":"/orders/order_reception","exceptionName":
"MissingServletRequestPartException","message":"Required request part 'uploadFile' is not
present",
"rootExceptionName":"MissingServletRequestPartException",
"rootMessage":"MissingServletRequestPartException: Required request part 'uploadFile' is not present"}
#RestController
#RequestMapping(value = "/orders")
#Validated
class OrderController{
#PostMapping(path = "/order_reception")
public ResponseEntity receiveData(#RequestPart MultipartFile uploadFile,
HttpServletRequest request,
HttpServletResponse response) {
if (!uploadFile.isEmpty()) {
try {
Reader reader = new InputStreamReader(request.getInputStream()));
... save file
return new ResponseEntity<>(HttpStatus.HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
#Test
public void sendData() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Order order = repository.getOrder("1233333");
List<Order> orderList = new ArrayList<>():
resourceList.add(order);
MockMultipartFile orderFile = new MockMultipartFile("order-data", "order.json", "application/json",
mapper.writeValueAsString(orderList).getBytes(Charset.defaultCharset()));
mockMvc.perform(multipart("/orders/order_reception")
.file(orderFile))
.andExpect(status().isCreated())
.andDo(document("send-order",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())));
}
Thank you Marten Deinum, your suggestion that the file name was wrong fixed it.
I simply changed name in the MockMultipartFile( "uploadsFile", ...)

Spring Boot MVC to allow any kind of content-type in controller

I have a RestController that multiple partners use to send XML requests. However this is a legacy system that it was passed on to me and the original implementation was done in a very loose way in PHP.
This has allowed to clients, that now they refuse to change, to send different content-types (application/xml, text/xml, application/x-www-form-urlencoded) and it has left me with the need to support many MediaTypes to avoid returning 415 MediaType Not Supported Errors.
I have used the following code in a configuration class to allow many media types.
#Bean
public MarshallingHttpMessageConverter marshallingMessageConverter() {
MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
converter.setMarshaller(jaxbMarshaller());
converter.setUnmarshaller(jaxbMarshaller());
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
MediaType.TEXT_XML, MediaType.TEXT_PLAIN, MediaType.APPLICATION_FORM_URLENCODED, MediaType.ALL));
return converter;
}
#Bean
public Jaxb2Marshaller jaxbMarshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(CouponIssuedStatusDTO.class, CouponIssuedFailedDTO.class,
CouponIssuedSuccessDTO.class, RedemptionSuccessResultDTO.class, RedemptionResultHeaderDTO.class,
RedemptionFailResultDTO.class, RedemptionResultBodyDTO.class, RedemptionDTO.class, Param.class,
ChannelDTO.class, RedeemRequest.class);
Map<String, Object> props = new HashMap<>();
props.put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setMarshallerProperties(props);
return marshaller;
}
The controller method is this:
#PostMapping(value = "/request", produces = { "application/xml;charset=UTF-8" }, consumes = MediaType.ALL_VALUE)
public ResponseEntity<RedemptionResultDTO> request(
#RequestHeader(name = "Content-Type", required = false) String contentType,
#RequestBody String redeemRequest) {
return requestCustom(contentType, redeemRequest);
}
This endpoint is hit by all clients. It is only one last client giving me trouble. They are sending content-type = application/x-www-form-urlencoded; charset=65001 (UTF-8)": 65001 (UTF-8)
Due to the way the charset is sent, Spring Boot refuses to return anything but 415. Not even MediaType.ALL seems to have any effect.
Is there a way to make Spring allow this to reach me ignoring the content-type? Creating a filter and changing the content type was not feasible since the HttpServletRequest is not allowing to mutate the content-type. I am out of ideas but I really think there has to be a way to allow custom content-types.
UPDATE
If I remove the #RequestBody then I don't get the error 415 but I have no way to get the request body since the HttpServletRequest reaches the Controller action empty.
You best case is to remove the consumes argument from the RequestMapping constructor. The moment you have it added, spring will try to parse it into known type MediaType.parseMediaType(request.getContentType()) & which tries to create a new MimeType(type, subtype, parameters) and thus throws exception due to invalid charset format being passed.
However, if you remove the consumes, and you wanna validate/restrict the incoming Content-Type to certain type, you can inject HttpServletRequest in your method as parameter, and then check the value of request.getHeader(HttpHeaders.CONTENT_TYPE).
You also have to remove the #RequestBody annotation so Spring doesn't attempt to parse the content-type in attempt to unmarshall the body. If you directly attempt to read the request.getInputStream() or request.getReader() here, you will see null as the stream has already been read by Spring. So to get access to input content, use spring's ContentCachingRequestWrapper inject using Filter and then you can later repeatedly read the content as it's cached & not reading from original stream.
I am including some code snippet here for reference, however to see executable example, you can refer my github repo. Its a spring-boot project with maven, once you launch it, you can send your post request to http://localhost:3007/badmedia & it will reflect you back in response request content-type & body. Hope this helps.
#RestController
public class BadMediaController {
#PostMapping("/badmedia")
#ResponseBody
public Object reflect(HttpServletRequest request) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.createObjectNode();
((ObjectNode) rootNode).put("contentType", request.getHeader(HttpHeaders.CONTENT_TYPE));
String body = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), StandardCharsets.UTF_8);
body = URLDecoder.decode(body, StandardCharsets.UTF_8.name());
((ObjectNode) rootNode).put("body", body);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
}
}
#Component
public class CacheRequestFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest cachedRequest
= new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
//invoke caching
cachedRequest.getParameterMap();
chain.doFilter(cachedRequest, servletResponse);
}
}

spring boot HttpServletResponse not setting file name

I have a rest service like this:
import org.apache.tomcat.util.http.fileupload.IOUtils;
#RequestMapping(value = "/xxx", method = GET)
public void getExcel(HttpServletResponse resp) {
resp.setHeader("Content-Disposition", "attachment; filename=\"NAME.xlsx\"");
resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
try (ServletOutputStream outputStream = resp.getOutputStream()) {
IOUtils.copy(A-VALID-FILE-INPUT-STREAM, outputStream);
resp.flushBuffer();
} catch (IOException e) {
throw new AppException(e);
}
}
the problem is that every time I call this service the default save name is 'response', I have tried returning HttpEntity<byte[]>, create objects like HttpHeaders() but nothing changes.
Any help is appreciated
If you are using postman take a look at https://github.com/postmanlabs/postman-app-support/issues/2082
Seems that you will need to wait until this issue will be addressed by postman team.

Spring asyncRestTemplate for SOAP Services

I am trying to call a soap service using Spring's AsyncRestTemplate.
I know that AsyncRestTemplate supports only rest call and spring-ws is there if we need to make soap calls. But Spring-ws doesn't support Async calls and uses JDK's HttpURLConnection class for doing http call and I wanted to make async soap webservices call.
Below is my code for creating the soapenvelop using JDKs saaj api.
public SOAPEnvelope createSoapEnvelope(Employee obj){
MessageFactory factory = MessageFactory.newInstance();
SOAPMessage message = factory.createMessage();
SOAPPart soapPart = message.getSOAPPart();
try {
SOAPEnvelope envelope = soapPart.getEnvelope();
SOAPBody body = envelope.getBody();
QName bodyName = new QName(Constants.SERVICE_NAMESPACE);
SOAPBodyElement bodyElement = body.addBodyElement(bodyName);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
// Marshal the Object to a Document
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(obj, document);
body.addDocument(document);
return envelope;
}catch (SOAPException | JAXBException | ParserConfigurationException e){
LOGGER.error("Unable to marshal ",e);
}
return null;
}
I set this soap envelop in HttpEntity to make the call though rest template like below
MultiValueMap headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT,"text/xml;charset=UTF-8");
headers.set(HttpHeaders.CONTENT_TYPE,"text/xml;charset=UTF-8");
headers.set(HttpHeaders.ACCEPT_ENCODING,"gzip,deflate");
headers.set("SOAPAction", Constants.SOAP_ACTION);
HttpEntity<SOAPEnvelope> soapEntity = new HttpEntity<>(soapEnvelope,headers);
ListenableFuture<ResponseEntity<SOAPEnvelope>> future=
restTemplate.postForEntity(url,
,soapEntity,SOAPEnvelope.class);
After doing this I get exception org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.sun.xml.internal.messaging.saaj.soap.ver1_1.Envelope1_1Impl] and content type [text/xml;charset=UTF-8]
I understood from this exception that Spring's HttpMessageConverters are not able to marshal SoapEnvelop and that's why this client is not able to submit the request.
I need help in writing custom message converter and registering it with rest template. All suggestions are welcome.
Thanks

How to handle exceptions with Swagger?

I am building some test APIs using swagger (1.5) and JAX-rs with Jersey (1.13) and I m trying to implement exception handling. For example I have the following code when receiving the results from my DB (Elasticsearch)
#POST
#Path("/category")
#ApiOperation(value="returns products")
#Produces({ "application/json" })
public Response getPostCategories(
#ApiParam(value="keyphrase, required=true) #QueryParam("keyphrase") String keyphrase,
#ApiParam(value="category) #QueryParam("category") String category,
#Context SecurityContext securityContext)
throws WebApplicationException {
SearchRequest searchRequest = new SearchRequest();
searchRequest.setKeyphrase(keyphrase);
searchRequest.setCategory(category);
SearchCategoryQuery categoryQuery = new SearchCategoryQuery();
String searchResponse = null;
try
{
searchResponse = categoryQuery.searchCategory(searchRequest);
}
catch (WebApplicationException ex)
{
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("results no found").type(javax.ws.rs.core.MediaType.APPLICATION_JSON).build());
}
return Response.ok(searchResponse).build();
}
However, in the output swagger always prints the same response
What I need instead is to receive the error messages I specify in each exception. Any ideas?
Swagger by itself does not handle application exceptions as yet.
You will either need to create custom Exception classes (that extend java.lang.exception) or use the existing ones (like WebApplicationException that you are already using) and make the API definition throw these errors. So basically you need to use Java/J2EE/Jersey to throw proper exceptions. Swagger UI will display them for you.
Check this link for details on REST exception handling with Spring.

Resources