What's the equivalent of CloseableService in Quarkus / RestEasy - quarkus

I am migrating a REST-Service from Jersey to Quarkus/RestEasy. Jersey can inject different Context objects into the Resource classes, among them is a CloseableService that you can use to robustly handle Closeable resources, no matter what goes wrong with the request (Runtime Exceptions, Aborted Connections, etc.).
The signature of a resource method looks roughly like this:
#Path("/datasets/{dataset}")
#Consumes("application/json")
#Produces("application/json")
#GET
public Stream<TSRecord> getDatasetDataJson(
#PathParam("dataset") final String dsName,
#QueryParam("parameter") final Parameter parameter,
#Context final CloseableService closer
) throws NoSuchDatasetException {
final Stream<TSRecord> stream = database.stream(dsName, parameter);
closer.add(stream::close);
return stream;
}
What is the equivalent of a CloseableService in Quarkus/RestEasy?

Related

Cannot get Spring Boot to lazily resolve a multipart file

I have created a Spring Boot 2 demo application with the Spring Initializr and added the controller below:
#Controller
#RequestMapping("/demo")
public class UploadController {
private final static Logger LOG = LoggerFactory.getLogger(UploadController.class);
#PostMapping("/upload")
public ResponseEntity<String> uploadFile(
#RequestParam("metadata") MultipartFile metadata,
#RequestParam("payload") MultipartFile payload) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map metadataMap = mapper.readValue(metadata.getInputStream(), Map.class);
LOG.info("Received call to upload file {}", metadataMap.get("filename"));
LOG.info("File size: {}", payload.getBytes().length);
LOG.info("File {} successfully uploaded", metadataMap.get("filename"));
return ResponseEntity.ok().build();
}
}
I then added an application.yaml file containing this configuration:
spring:
servlet:
multipart:
max-file-size: 2000000MB
max-request-size: 2000000MB
resolve-lazily: true
My goal is to have the controller parse and log the metadata file before it starts reading the payload file, but the resolve-lazily setting seems to be ignored by Boot: the code inside the controller won't be executed until the whole body is read.
I use the command below to test the controller:
curl -F metadata=#metadata.json -F payload=#payload.bin http://localhost:8080/demo/upload
Is there anything wrong with my code/configuration? Am I getting the meaning of the setting right?
At present, if you want to avoid reading (and buffering) the whole body all at once, I think you will have to provide your own parser, as described in the answers here. What would be really interesting (but generally unnecessary) would be to do so in the form of a new MultipartResolver implementation.
There are two existing implementations documented for interface MultipartResolver, and both supply a function setResolveLazily(boolean) (standard), (commons). I have tried with both, and neither seem to allow for parsing or streaming multipart files or parameters independently.
Default is "false", resolving the multipart elements immediately, throwing corresponding exceptions at the time of the resolveMultipart(javax.servlet.http.HttpServletRequest) call. Switch this to "true" for lazy multipart parsing, throwing parse exceptions once the application attempts to obtain multipart files or parameters.
Despite what it says in the documentation, I have found that once you call resolveMultipart, the entire body is parsed and buffered before the call returns. I know this because I can watch the temp-files being created.
One note about "Is there anything wrong with my code"...
Answer: Yes, because by using #RequestParam you have indirectly asked Spring to resolve your parameters ahead of time, before your controller is ever called. What you should be able to do instead (if the documentation were correct) is request the parameters independently from inside your controller:
Configuration (application.properties):
spring.servlet.multipart.enabled = true
spring.servlet.multipart.resolve-lazily = true
Controller:
#PostMapping(path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> postUpload(HttpServletRequest rawRequest) {
multipartResolver.setResolveLazily(true); // unclear why this is exists
MultipartHttpServletRequest request = multipartResolver.resolveMultipart(rawRequest);
String p1 = request.getParameter("first-parameter");
String p2 = request.getParameter("second-parameter");
System.out.println("first-parameter="+p1+", second-parameter"+p2);
multipartResolver.cleanupMultipart(request);
return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
}
One useful aspect of resolve-lazily that I have discovered is that it allows you to write your own parser for some rest controllers while using the built-in parser for others (see my answer here). In other words, you don't have to use spring.servlet.multipart.enabled = false to get your parser to work. This is a minor breakthrough relative to other advice that I had seen previously.

Spring cloud stream messaging system(RabbitMQ) implementation using Rest Controller(Restful API)

From past few days i'm trying to implement the Spring cloud stream messaging system using RestController, but it is not happening through the current implementation.
For this sample code i'm going to add RestController
#EnableBinding(Source.class)
#EnableConfigurationProperties(TimeSourceOptionsMetadata.class)
public class TimeSource {
#Autowired
private TimeSourceOptionsMetadata options;
#InboundChannelAdapter(value = Source.OUTPUT)
public String timerMessageSource() {
return new SimpleDateFormat(this.options.getFormat()).format(new Date());
}
}
But the #InboundChannelAdapter cannot accept any parameters from RequestMapping Get Method URL.At the end what i need is to add message to the broker using Restful API Get method from api call. which is the best way to do it?, I couldn't figure out any best process from internet.
spring cloud team already provided a source application that listens for HTTP requests and emits the body as a message payload. If the Content-Type matches text/* or application/json, the payload will be a String, otherwise the payload will be a byte array.
github link
You can go with this or if you want to write it yourself, you can do it like below:
#RestController
#EnableBinding(Source.class)
public class RestSource {
#Autowired
private Source channels;
#RequestMapping(path = "/", method = POST, consumes = {"application/json" })
#ResponseStatus(HttpStatus.ACCEPTED)
public void handleRequest(#RequestBody String body, #RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) {
sendMessage(body, contentType);
}
private void sendMessage(Object body, Object contentType) {
channels.output().send(MessageBuilder.createMessage(body,
new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType))));
}
}

spring boot override default REST exception handler

I am not able to override default spring boot error response in REST api. I have following code
#ControllerAdvice
#Controller
class ExceptionHandlerCtrl {
#ResponseStatus(value=HttpStatus.UNPROCESSABLE_ENTITY, reason="Invalid data")
#ExceptionHandler(BusinessValidationException.class)
#ResponseBody
public ResponseEntity<BusinessValidationErrorVO> handleBusinessValidationException(BusinessValidationException exception){
BusinessValidationErrorVO vo = new BusinessValidationErrorVO()
vo.errors = exception.validationException
vo.msg = exception.message
def result = new ResponseEntity<>(vo, HttpStatus.UNPROCESSABLE_ENTITY);
result
}
Then in my REST api I am throwing this BusinessValidationException. This handler is called (I can see it in debugger) however I still got default spring boot REST error message. Is there a way to override and use default only as fallback? Spring Boot version 1.3.2 with groovy. Best Regards
Remove #ResponseStatus from your method. It creates an undesirable side effect and you don't need it, since you are setting HttpStatus.UNPROCESSABLE_ENTITY in your ResponseEntity.
From the JavaDoc on ResponseStatus:
Warning: when using this annotation on an exception class, or when setting the reason attribute of this annotation, the HttpServletResponse.sendError method will be used.
With HttpServletResponse.sendError, the response is considered complete and should not be written to any further. Furthermore, the Servlet container will typically write an HTML error page therefore making the use of a reason unsuitable for REST APIs. For such cases it is preferable to use a ResponseEntity as a return type and avoid the use of #ResponseStatus altogether.
I suggest you to read this question: Spring Boot REST service exception handling
There you can find some examples that explain how to combine ErrorController/ ControllerAdvice in order to catch any exception.
In particular check this answer:
https://stackoverflow.com/a/28903217/379906
You should probably remove the annotation #ResponseStatus from the method handleBusinessValidationException.
Another way that you have to rewrite the default error message is using a controller with the annotation #RequestMapping("/error"). The controller must implement the ErrorController interface.
This is the error controller that I use in my app.
#RestController
#RequestMapping("/error")
public class RestErrorController implements ErrorController
{
private final ErrorAttributes errorAttributes;
#Autowired
public MatemoErrorController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return "/error";
}
#RequestMapping
public Map<String, Object> error(HttpServletRequest aRequest) {
return getErrorAttributes(aRequest, getTraceParameter(aRequest));
}
private boolean getTraceParameter(HttpServletRequest request) {
String parameter = request.getParameter("trace");
if (parameter == null) {
return false;
}
return !"false".equals(parameter.toLowerCase());
}
private Map<String, Object> getErrorAttributes(HttpServletRequest aRequest, boolean includeStackTrace)
{
RequestAttributes requestAttributes = new ServletRequestAttributes(aRequest);
return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
} }

Building custom API over Spring Websockets

I'm have to implement custom API over Websockets that requires:
Custom WAMP-like subprotocol
Path parameters in socket URI
So I've following questions:
Is there any documentation or guides on implementing custom subprotocols in Spring? Protocol requires that exact version must be specified in the Sec-Websocket-Protocol field. Where this field could be read on server side?
What is a proper way to pass path parameters into a message handler? I could use ant patterns in handler registration
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(customHandler(), "/api/custom/{clientId}");
}
but those seems not available at TextWebSocketHandler. I'm solved this for now by extending default HttpSessionHandshakeInterceptor in a following way:
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private static final UriTemplate URI_TEMPLATE = new UriTemplate("/api/custom/{clientId}");
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
Map<String, String> segments = URI_TEMPLATE.match(request.getURI().getPath());
attributes.put("CLIENTID", segments.get("clientId"));
return super.beforeHandshake(request, response, wsHandler, attributes);
}
}
and then accessing it in TextWebSocketHandler:
public class CustomHandler extends TextWebSocketHandler {
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
String clientId = session.getAttributes().get("CLIENTID");
...
session.sendMessage(response);
}
}
but this method, in my opinion, is a bit clunky. Is there more proper way to solve this?
Thanks.
The best advice I could give is to follow the example of the sub-protocol support that's built in -- starting with SubProtocolWebSocketHandler and the SubProtocolHandler's it delegates to including the StompSubProtocolHandler implementation. The SubProtocolWebSocketHandler is further connected to "clientInbound" and "clientOutbound" channels which are then used to form a processing flow as well as to provide thread boundaries.
There is a description for the processing flow for STOMP http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow which includes delegating messages to annotated controllers and/or to a message broker which can also send messages back downstream to clients.
Essentially the StompSubProtocolHandler translates to and from a WebSocketMessage and a Spring Message with protocol-specific content. So that controllers, message brokers, or any other consumer of the messages from the client inbound channel are decoupled and unaware from the WebSocket transport layer. Many of the facilities built around the building, sending, and processing of such sub-protocol messages are meant to be potentially usable for support of other STOMP-like protocols. That includes all the classes in the org.springframework.messaging.simp package.
As for URL path parameters, Spring doesn't provide anything at the WebSocket level which is mostly a transport layer. Most of the interesting stuff happens at the sub-protocol level. For example for STOMP a MessageMapping is supported based on the destination header along with a #DestinationVariable which is comparable to using #PathVariable in Spring MVC but based on the destination header, not the URL.

Spring MVC + DeferredResult add Hateoas stuff

For the rest interface the Spring MVC + RxJava + DeferredResult returned from controllers is used.
I am thinking about adding Hateoas support to the endpoints. The natural choice would be the Spring Hateoas. The problem is that Spring Hateoas would not work in the asynchronous/multi-threading environment since it uses ThreadLocal.
Is there any way to workaround that constraint? I do not think so but maybe someone has any suggestions.
Has anyone used other APIs to add Hateoas support to the rest endpoints?
Thank you.
So the solution I've used is to closure in the request attributes and then apply them as part of a lift operator
public class RequestContextStashOperator<T> implements Observable.Operator<T, T> {
private final RequestAttributes attributes;
/**
* Spring hateoas requires the request context to be set but observables may happen on other threads
* This operator will reapply the context of the constructing thread on the execution thread of the subscriber
*/
public RequestContextStashOperator() {
attributes = RequestContextHolder.currentRequestAttributes();
}
#Override
public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
return new Subscriber<T>() {
#Override
public void onCompleted() {
subscriber.onCompleted();
}
#Override
public void onError(Throwable e) {
subscriber.onError(e);
}
#Override
public void onNext(T t) {
RequestContextHolder.setRequestAttributes(attributes);
subscriber.onNext(t);
}
};
}
}
which you can then use on an observable like
lift(new RequestContextStashOperator<>())
as long as the object is created in the same thread as the request. You can then use a map after in the observable chain to map your object up to being a resource and add your hateoas links in.
So answer is a bit late, but probably someone will find it useful.
You are right about ThreadLocal - if you generate hateoas links in different thread, then it fails with exception. I found some kind of workaround for this:
#RequestMapping(path = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
DeferredResult<ResponseEntity<ProductResource>> example(#PathVariable("id") final String productId, final HttpServletRequest request) {
final DeferredResult<ResponseEntity<ProductResource>> deferredResult = new DeferredResult<>();
request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
final RequestAttributes requestAttributes = new ServletRequestAttributes(request);
productByIdQuery.byId(UUID.fromString(productId)).subscribe(productEntity -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
deferredResult.setResult(result, HttpStatus.OK))
}, deferredResult::setErrorResult);
return deferredResult;
}
So as you see, I save RequestAttributes so I can set them later in the callback. This solves just part of the problem - you'll get another exception because you'll loose contextPath attribute. To avoid this save it explicitly:
request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
After those changes everything seems to work, but looks messy of course. I hope that somebody can provide more elegant solution.

Resources