Type mismatch: cannot convert from String to ListenableFuture<String> - spring

I'm trying to implementing non-blocking call. in spring 4, But unfortunately it's throwing the below error.
Type mismatch: cannot convert from String to ListenableFuture
and also same error can not able convert from Map to ListenableFuture>.
My Method call stack is as below.
ListenableFuture<Map<String,String>> unusedQuota = doLogin(userIdentity,request,"0");
doLogin login simply return Map
is there any converter required?
what changes would be required ?
Thanks.
public class MyController {
final DeferredResult<Map<String,String>> deferredResult = new DeferredResult<Map<String,String>>(5000l);
private final Logger log = LoggerFactory.getLogger(MyController.class);
#Inject
RestTemplate restTemplate;
#RequestMapping(value = "/loginservice", method = RequestMethod.GET)
#Timed
public DeferredResult<Map<String,String>> loginRequestService(#RequestParam String userIdentity,HttpServletRequest request) throws Exception {
deferredResult.onTimeout(new Runnable() {
#Override
public void run() { // Retry on timeout
deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("Request timeout occurred."));
}
});
#SuppressWarnings("unchecked")
ListenableFuture<Map<String,String>> unusedQuota = doLogin(userIdentity,request);
unusedQuota.addCallback(new ListenableFutureCallback<Map<String,String>>() {
#SuppressWarnings("unchecked")
#Override
public void onSuccess(Map<String, String> result) {
// TODO Auto-generated method stub
deferredResult.setResult((Map<String, String>) ResponseEntity.ok(result));
}
#Override
public void onFailure(Throwable t) {
// TODO Auto-generated method stub
deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(t));
}
});
return deferredResult;
}
private Map<String,String> doLogin(String userIdentity,HttpServletRequest request) throws Exception{
Map<String,String> unusedQuota=new HashMap<String,String>();
unusedQuota.put("quota", "100");
return unusedQuota;
}
}
}

You are NOT passing the Map object when there is an exception which is causing the issue, so your controller method needs to be changed as shown below, also move deferredResult object inside the Controller method as you should share the same instance of deferredResult for different user request.
public class MyController {
#Autowired
private TaskExecutor asyncTaskExecutor;
#RequestMapping(value = "/loginservice", method = RequestMethod.GET)
#Timed
public DeferredResult<Map<String,String>> loginRequestService(#RequestParam String userIdentity,HttpServletRequest request) throws Exception {
final DeferredResult<Map<String,String>> deferredResult = new DeferredResult<Map<String,String>>(5000l);
deferredResult.onTimeout(new Runnable() {
#Override
public void run() { // Retry on timeout
Map<String, String> map = new HashMap<>();
//Populate map object with error details with Request timeout occurred.
deferredResult.setErrorResult(new ResponseEntity
<Map<String, String>>(map, null,
HttpStatus.REQUEST_TIMEOUT));
}
});
ListenableFuture<String> task = asyncTaskExecutor.submitListenable(new Callable<String>(){
#Override
public Map<String,String> call() throws Exception {
return doLogin(userIdentity,request);
}
});
unusedQuota.addCallback(new ListenableFutureCallback<Map<String,String>>() {
#SuppressWarnings("unchecked")
#Override
public void onSuccess(Map<String, String> result) {
// TODO Auto-generated method stub
deferredResult.setResult((Map<String, String>) ResponseEntity.ok(result));
}
#Override
public void onFailure(Throwable t) {
Map<String, String> map = new HashMap<>();
//Populate map object with error details
deferredResult.setErrorResult(new ResponseEntity<Map<String, String>>(
map, null, HttpStatus.INTERNAL_SERVER_ERROR));
}
});
return deferredResult;
}
}
Also, you need to ensure that you are configuring the ThreadPoolTaskExecutor as explained in the example here.

Related

How to Receive Response from Websocket Unit Test in Springboot

I am new to websockets and I am trying to write a unit test.
My unit test runs fine but it has following two issue
Idk why but it forces me to expect same object that is being sent as an input(i.e WebSocketRequestData) to the websocket instead of the actual response from the websocket which is WebSocketData
And it returns an empty object as result so it passes NotNull assertion.
Can anyone please clear out this confusion for me!
And also what is the right way to get response from the my websocket in unit test?
here is the code for my websocketTest Class
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ServerWebSocketTest {
#LocalServerPort
private Integer port;
static final String WEBSOCKET_TOPIC = "/user/locationrealtimedata/item" ;
BlockingQueue<WebSocketRequestData> blockingQueue;
WebSocketStompClient stompClient;
#BeforeEach
public void setup() {
blockingQueue = new LinkedBlockingDeque<>();
stompClient = new WebSocketStompClient(new SockJsClient(
asList(new WebSocketTransport(new StandardWebSocketClient()))));
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
}
#Test
public void shouldReceiveAMessageFromTheServer() throws Exception {
StompSession session = stompClient
.connect(getWsPath(), new DefaultStompFrameHandler() {
})
.get(1, TimeUnit.SECONDS);
session.subscribe(WEBSOCKET_TOPIC, new DefaultStompFrameHandler());
WebSocketRequestData webSocketRequestData = new WebSocketRequestData();
webSocketRequestData.setUserId("usr-1");
webSocketRequestData.setAccountId("acc-1");
webSocketRequestData.setGroupId("grp-1");
session.send("/wsconn/start", webSocketRequestData);
WebSocketRequestData responseObj = blockingQueue.poll(15, TimeUnit.SECONDS);
Assertions.assertNotNull(responseObj);
}
class DefaultStompFrameHandler extends StompSessionHandlerAdapter{
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return WebSocketRequestData.class;
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
blockingQueue.offer((WebSocketRequestData) o); // instead of **WebSocketData** it forces me to add casting for **WebSocketRequestData**
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
}
}
private String getWsPath() {
return String.format("ws://localhost:%d/location_services/locationrealtimedata", port);
}
}
Thanks in advance
You are not forced to use the same Java class for the input and response type.
The request type is what you use within session.send("/endpoint", payload); in your case that's WebSocketRequestData:
WebSocketRequestData webSocketRequestData = new WebSocketRequestData();
webSocketRequestData.setUserId("usr-1");
webSocketRequestData.setAccountId("acc-1");
webSocketRequestData.setGroupId("grp-1");
session.send("/wsconn/start", webSocketRequestData);
When it comes to consuming messages you specify the actual response type you expect when implementing StompFrameHandler and overriding getPayloadType.
So instead of implementing StompSessionHandlerAdapter, use the StompFrameHandler interface and implement it as the following:
class DefaultStompFrameHandler extends StompSessionHandlerAdapter{
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return WebSocketData.class; // or any other class your expect
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
blockingQueue.offer((WebSocketData) o);
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
}
}
Also make sure your BlockingQueue is using the correct type BlockingQueue<WebSocketData> blockingQueue

Springboot 2.1.x Webflux functional endpoints - How to perform input validation?

I am trying to integrate validation in this code, but it fails without any error (just a blank page being returned):
#Component
#Slf4j
public class EthereumAccountController implements ClarityControllerMono {
private final Pipeline queryPipeline;
private final Pipeline commandPipeline;
private final Web3jService web3;
private final RequestHandler requestHandler;
public EthereumAccountController(#Qualifier("queryPipelinr") Pipeline queryPipeline, #Qualifier("commandPipelinr") Pipeline commandPipeline, Web3jService web3, RequestHandler requestHandler) {
this.queryPipeline = queryPipeline;
this.commandPipeline = commandPipeline;
this.web3 = web3;
this.requestHandler = requestHandler;
}
public Mono<ServerResponse> createAccount(ServerRequest serverRequest) {
return requestHandler.requireValidBody(body -> getJsonErrsResp("testtttt"), serverRequest, AccountRequestDTO.class);
}
}
public interface ClarityControllerMono {
default Mono<ServerResponse> getJsonSuccessResp (Object object) {
Map<String, Object> result = new LinkedHashMap<>();
result.put("status", "success");
result.put("data", object);
return ok()
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromPublisher(Mono.just(toJSON(result)), String.class));
}
default Mono<ServerResponse> getJsonErrsResp (Object object) {
Map<String, Object> result = new LinkedHashMap<>();
result.put("status", "error");
result.put("message", object);
return ok()
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromPublisher(Mono.just(toJSON(result)), String.class));
}
private String toJSON(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
return Unchecked.function(objectMapper::writeValueAsString).apply(object);
}
}
Repo here
This is where the code fails
#Component
public class RequestHandler {
private final Validator validator;
public RequestHandler(Validator validator) {
this.validator = validator;
}
public <BODY> Mono<ServerResponse> requireValidBody(
Function<Mono<BODY>, Mono<ServerResponse>> block,
ServerRequest request, Class<BODY> bodyClass) {
return request
.bodyToMono(bodyClass)
.flatMap(
body -> validator.validate(body).isEmpty()
? block.apply(Mono.just(body))
: ServerResponse.unprocessableEntity().build()
);
}
}
This is the repo where I found this interesting solution that I want to implement (the bit of code where it's failing)
https://github.com/jeroenbellen/Validate-functional-endpoints-in-Spring/blob/master/src/main/java/foo/bar/springfunctionalwebvalidation/controller/RequestHandler.java
Thank you in advance.
EDIT
public <BODY> Mono<ServerResponse> requireValidBody(
Function<Mono<BODY>, Mono<ServerResponse>> block,
ServerRequest request, Class<BODY> bodyClass) {
Hooks.onOperatorDebug();
return request
.bodyToMono(bodyClass)
.doOnError(Throwable::printStackTrace)
.flatMap(
body -> {
log.info("Emptyness" +validator.validate(body).isEmpty() + "");
return validator.validate(body).isEmpty()
? block.apply(Mono.just(body))
: ServerResponse.unprocessableEntity().build();
}
).doOnError(Throwable::printStackTrace);
doOnError does not out put anything.

spring-data-rest: Validator not being invoked

I am using springboot 2.0.1.RELEASE with spring-data-rest and followed the workaround mentioned here and my Validator is still not being invoked. Here are the details:
ValidatorRegistrar: Workaround for a bug
#Configuration
public class ValidatorRegistrar implements InitializingBean {
private static final List<String> EVENTS;
static {
List<String> events = new ArrayList<String>();
events.add("beforeCreate");
events.add("afterCreate");
events.add("beforeSave");
events.add("afterSave");
events.add("beforeLinkSave");
events.add("afterLinkSave");
events.add("beforeDelete");
events.add("afterDelete");
EVENTS = Collections.unmodifiableList(events);
}
#Autowired
ListableBeanFactory beanFactory;
#Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
#Override
public void afterPropertiesSet() throws Exception {
Map<String, Validator> validators = beanFactory.getBeansOfType(Validator.class);
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
EVENTS.stream().filter(p -> entry.getKey().startsWith(p)).findFirst()
.ifPresent(p -> validatingRepositoryEventListener.addValidator(p, entry.getValue()));
}
}
}
Validator class:
#Component("beforeSaveBidValidator")
public class BeforeSaveBidValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Bid.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Bid bid = (Bid)target;
if (!bid.getAddendaAcknowledged()) {
errors.rejectValue("addendaAcknowledged",
"addendaAcknowledged is not true");
}
}
}
Custom RestController for Bids:
#RestController
#RequestMapping(path = "/bids")
public class BidController {
private BidRepository bidRepository;
#Autowired
public BidController(
BidRepository bidRepository) {
this.bidRepository = bidRepository;
}
#PutMapping("{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
Rest Client Test Code:
Bid bid = new Bid()
...
bid.setAddendaAcknowledged(false)
Map<String, String> uriVariables = new HashMap<String, String>()
uriVariables.put("id", bid.id)
HttpHeaders headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
HttpEntity<Bid> entity = new HttpEntity<>(bid, headers)
ResponseEntity<String> response = restTemplate.exchange(
"/bids/{id}", HttpMethod.PUT, entity, Bid.class, bid.id)
// Expected: response.statusCode == HttpStatus.BAD_REQUEST
// Found: response.statusCode == HttpStatus.OK
// Debugger showed that Validator was never invoked.
Any idea what I am missing?
You are trying to use your validator with custom controller, not SDR controller. In this case you can just add it to your controller with #InitBinder annotation:
#RestController
#RequestMapping("/bids")
public class BidController {
//...
#InitBinder("bid") // add this parameter to apply this binder only to request parameters with this name
protected void bidValidator(WebDataBinder binder) {
binder.addValidators(new BidValidator());
}
#PutMapping("/{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
#Component annotation on your validator is not necessary as well as ValidatorRegistrar class.
How to use validators with SDR controllers you can read in my another answer.

Throwing Custom Exception with HTTP Response From Spring Validator

I have implemented a custom Validator in Spring which is called inside an overridden Jackson de-serializer. If validation fails, I want the HTTP response code to be a 403 Forbidden as defined in my ControllerAdvice.
However, the response is always 400 Bad Request.
public class InterceptedDeserializer extends StdDeserializer<Object> implements ResolvableDeserializer
{
public InterceptedDeserializer(JsonDeserializer<?> defaultDeserializer)
{
super(Object.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, My403Exception
{
this.classFieldValidator = ServletUtils.findWebApplicationContext().getBean(ClassFieldValidator.class);
Object deserializedObject = defaultDeserializer.deserialize(jp, ctxt);
Errors errors = new BeanPropertyBindingResult(deserializedObject, deserializedObject.getClass().getName());
classFieldValidator.validate(deserializedObject, errors);
if(errors.hasErrors() || errors.hasFieldErrors()){
throw new My403Exception("No funny business");
}
return deserializedObject;
}
}
#ControllerAdvice
public class ValidationControllerAdvice {
private static final Logger log = LoggerFactory.getLogger(ValidationControllerAdvice.class);
private final StringWriter sw = new StringWriter();
#ResponseBody
#ExceptionHandler(My403Exception.class)
#ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse my403Exception(My403Exception e) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setErrorCode("my403");
errorResponse.setDescription(e.getMessage());
errorResponse.setMessage(e.getMessage());
e.printStackTrace(new PrintWriter(sw));
String eStackTrace = sw.toString();
log.error("My403 error message: " + e.getMessage() + "\nException Class:" + e.getClass() + "\nStack Trace:" + eStackTrace);
return errorResponse;
}
}
#ResponseStatus(value = HttpStatus.FORBIDDEN)
public class My403Exception extends RuntimeException{
private String message;
public My403Exception(String message) {
super(message);
this.message = message;
}
public My403Exception() {
}
#Override
public String getMessage() {
return message;
}
}
#ResponseStatus(HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST, path = "/thing")
public void createmyThing(#RequestParam(value = "thing") String thing, #RequestBody() #Valid MyThing thing) throws My403Exception {
thingService.createThing(thing);
}

Async Spring Rest Controller - Process future result

I am new to futures and I am trying to figure out how I can process the Listenable future in this senario:
#Controller
#EnableAutoConfiguration
public class ListenableFutureAsyncController {
#Autowired
IHeavyLiftingService heavyLiftingService;
#RequestMapping("/")
#ResponseBody
DeferredResult<String> home() {
// Create DeferredResult
final DeferredResult<String> result = new DeferredResult<>();
//Call to the async service
ListenableFuture<ResponseEntity<String>> future = heavyLiftingService.heavyLifting();
future.addCallback(
new ListenableFutureCallback<ResponseEntity<String>>() {
#Override
public void onSuccess(ResponseEntity<String> response) {
result.setResult(response.getBody());
}
#Override
public void onFailure(Throwable t) {
result.setErrorResult(t.getMessage());
}
});
// Return the thread to servlet container,
// the response will be processed by another thread.
return result;
}
}
How can I process the future here other than passing it back to the controller. Ex. What if I want to save the future string to the db?
#Service
public class HeavyLiftingServiceImpl implements IHeavyLiftingService {
public ListenableFuture<String> heavyLifting() {
AsyncRestTemplate asycTemp = new AsyncRestTemplate();
ListenableFuture<String> future = asycTemp.execute(url, method, requestCallback, responseExtractor, urlVariable);
/**
/Save future string to db
**/
return future;
}
}
I have found a way to do this using Spring's ListenableFutureAdapter
#Service
public class HeavyLiftingServiceImpl implements IHeavyLiftingService {
public ListenableFuture<String> heavyLifting() {
AsyncRestTemplate asycTemp = new AsyncRestTemplate();
ListenableFuture<String> future = asycTemp.execute(url, method, requestCallback, responseExtractor, urlVariable);
ListenableFutureAdapter<String, String> chainedFuture;
chainedFuture = new ListenableFutureAdapter<String, String>(future) {
#Override
protected String adapt(String adapteeResult)
throws ExecutionException {
String parsedString = parse(adapteeResult);
return adapteeResult;
}
};
return chainedFuture;
}
}
I would recommend using Guava's implementation of listenable future. I find it more readable and documentation on chaining them together is easier to find.

Resources