how to make spring boot mvc understand observables? - java

i want to return Observable from spring's mvc controller. it works with Single:
#GetMapping Object a() {return Single.just(1);}
as expected i get 1 when i query the server. but when i do the same with Observable:
#GetMapping Object a() {return Observable.just(1);}
the answer i got is {}. spring-mvc doesn't subscribe to returned Observable but simply serializes it to json. can spring-mvc understand Observable out of the box and i just messed up with some configuration? or do i have to register my custom handlers or install some plugins?

You can use Spring MVC Reactive (but it's not currently released as final version). It works with Reactor AND RxJava. You'll be able to write this sort of controller :
#Test
class ExampleController {
#RequestMapping("/hello")
public Single<String> hello() { return Single.just("world"); }
}
or You can write your own class adapter and transform a Single as a Spring DeferredResult (see this example)
This example cames from an Spring Boot Starter that you may wants to directly use.

There is a return value handler in spring-cloud-netflix. Below pom dependencies worked for me.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
I have tested this with rx 1.x
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>1.1.10</version>
</dependency>

If you can't upgrade to the spring-reactive, you can use the org.springframework.web.context.request.async.DeferredResult
public class ExampleController {
#RequestMapping("/hello")
public DeferredResult<ResponseEntity<String>> hello() {
DeferredResult<ResponseEntity<String>> deferredResult = new DeferredResult<>();
// your observable
Observable.just("world")
.subscribe(
text -> deferredResult.setResult(
ResponseEntity.accepted()
.contentType(MediaType.TEXT_PLAIN)
.body(text)),
error -> deferredResult.setResult(
ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT)
.build()
);
return deferredResult;
}
}

If you are on springboot 1.5
and want to return Observable from your restControllers
and avoid this message :
"No converter found for return value of type: class io.reactivex.internal.operators.observable.ObservableMap"
you have to add two class
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"PACKAGE CONTROLLER"})
public class DispatcherContextConfig extends WebMvcConfigurerAdapter {
#Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.add(new ObservableReturnValueHandler());
}
}
and
public class ObservableReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
#Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
return returnValue != null && supportsReturnType(returnType);
}
#Override
public boolean supportsReturnType(MethodParameter returnType) {
return Observable.class.isAssignableFrom(returnType.getParameterType());
}
#Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
final Observable<?> observable = Observable.class.cast(returnValue);
WebAsyncUtils.getAsyncManager(webRequest)
.startDeferredResultProcessing(new ObservableAdapter<>(observable), mavContainer);
}
public class ObservableAdapter<T> extends DeferredResult<T> {
public ObservableAdapter(Observable<T> observable) {
observable.subscribe(this::setResult, this::setErrorResult);
}
}
}
then
#GetMapping(path = "{idDocument}/ropObs")
public Observable<DocumentDto> getDocumentRopObs(#PathVariable String idDocument) {
DocumentDto r = documentService.getDocumentRopInfo(idDocument)
.map(dtoMapper::documentBusinessToDto)
.doOnError(error -> {throw new ApiErrorServerError(error.getMessage());})
.blockingSingle();
return Observable.just(r);
}
and a better practice
#GetMapping(path = "{idDocument}/ropObs2")
public Observable<DocumentDto> getDocumentRopObs2(#PathVariable String idDocument) {
return documentService.getDocumentRopInfo(idDocument).map(dtoMapper::documentBusinessToDto);
}

Related

Disable 1/yes/on and 0/no/off and allow only true/false to be passed via query-param or path-param

I want to disable Spring built-in deserialization of integer 0/1 and the strings
no/off yes/on that are mapped to false/true respectively when they are
passed via query-parameter or path-variable.
When any value that is not true/false is passed I want to throw IllegalArgumentException.
First I've implemented my own StringToBooleanConverter:
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
public class StringToBooleanConverter implements Converter<String, Boolean> {
#Override
public Boolean convert(String source) {
String value = source.trim().toLowerCase();
if ("true".equals(value)) {
return Boolean.TRUE;
} else if ("false".equals(value)) {
return Boolean.FALSE;
}
throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
}
}
And since I've provided an implementation of similar class spring will use my StringToBooleanConverter and not the one from org.springframework.core.convert.support
But here is where my problem arise. When I send e.g. 0 code is reaching to the IllegalArgumentException and then execution passed to CustomBooleanEditor in the
org.springframework.beans.propertyeditors and the method public void setAsText(#Nullable String text) throws IllegalArgumentException {} is invoked and does almost the same logic
as StringToBooleanConverter.
How can I disable Spring from calling this method and use only the StringToBooleanConverter?
Try registering your Converter as follows:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, Boolean.class);
registry.addConverter(new StringToBooleanConverter());
}
}
If this does not work, which I suspect. Try creating your own CustomBooleanEditor as follows:
#Configuration
public class Config {
#Bean
public CustomBooleanEditor getCustomBooleanEditor() {
return new CustomBooleanEditor("true", "false", false);
}
}
Configuring ObjectMapper disabling the following feature helped to solve the problem:
#Bean
#Primary
public ObjectMapper objectMapper() {
return JsonMapper.builder()
.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
.build();
}

Global exception handler never get called in micronaut 3 with project reactor

After upgrading the micronaut application from 2.5.12 to 3.0.0 and using the Project reactor as reactive stream. The Global exception handler method never get called.
public class GlobalException extends RuntimeException{
public GlobalException(Throwable throwable){super(throwable);}
}
#Produces
#Singleton
#Requires(classes = {GlobalException.class, ExceptionHandler.class})
public class GlobalExceptionHandler implements ExceptionHandler<GlobalException, HttpResponse> {
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#Override
public HttpResponse handle(HttpRequest request, GlobalException exception) {
LOG.error(exception.getLocalizedMessage());
LOG.error(exception.getCause().getMessage());
Arrays.stream(exception.getStackTrace()).forEach(item -> LOG.error(item.toString()));
return HttpResponse.serverError(exception.getLocalizedMessage());
}
}
On exception with below code, the handler method never get called
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager.find(searchCriteria).onErrorMap(throwable -> {
return new GlobalException(throwable);
});
}
I had this code Global exception handling in micronaut Java from rxjava 3 which was working fine, however, now with project reactor it is not working
If you only intend to produce a single element it should be annotated with #SingleResult Or use Mono
#Override
#SingleResult
public Flux<List<FindProductCommand>> freeTextSearch(String text) {
LOG.info("Controller --> Finding all the products");
return iProductManager.findFreeText(text)
.onErrorMap(throwable -> {
throw new GlobalException(throwable);
});
}
Try this one:
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager
.find(searchCriteria)
.onErrorResume(e -> Mono.error(new GlobalException("My exception", e));
}
One reason for this might be that in your code, looking at this bit:
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager.find(searchCriteria).onErrorMap(throwable -> {
return new GlobalException(throwable);
});
}
If the code iProductManager.find(searchCriteria) is calling a rest endpoint and getting a 404 not found, you will not get an error in the error handler. Rather the result is that you will get the equivalent of an Optional.empty().
If you want to force en error, you could change it to something like:
iProductManager.find(searchCriteria).switchIfEmpty(Mono.error(...))

Spring Integradion DSL answer to frontend

Good day! If i use transformer in my integration flow, i didn`t recieve answer to frontend, just waiting for responce. If i remove transformer, all is ok. Here is my controller method:
#GetMapping("/get/{name}")
public ResponseEntity<String> getSpaceShip(#PathVariable String name) {
SpaceShip spaceShip = new SpaceShip(name, 0);
gateway.spaceShipCreated(spaceShip);
return ResponseEntity.ok("Started!");
}
and Configuration:
#Configuration
public class SpaceShipConfiguration {
#MessagingGateway(defaultRequestChannel = "rename")
public interface Gateway {
SpaceShip spaceShipCreated(SpaceShip spaceShip);
}
#Bean
public IntegrationFlow spaceShipMoving() {
return IntegrationFlows.from("rename")
.handle("renameService", "rename")
.handle("fuelService", "addFuel")
//.transform(Transformers.toJson())
.handle("debug", "printMessage")
.get();
}
}
I got the error - my gateway
#MessagingGateway(defaultRequestChannel = "rename")
public interface Gateway {
SpaceShip spaceShipCreated(SpaceShip spaceShip);
}
must return the Object, but after transformation can't. Need just return void in my case.

Spring Boot cache not caching method call based on dynamic controller parameter

I am attempting to use Spring Boot Cache with a Caffeine cacheManager.
I have injected a service class into a controller like this:
#RestController
#RequestMapping("property")
public class PropertyController {
private final PropertyService propertyService;
#Autowired
public PropertyController(PropertyService propertyService) {
this.propertyService = propertyService;
}
#PostMapping("get")
public Property getPropertyByName(#RequestParam("name") String name) {
return propertyService.get(name);
}
}
and the PropertyService looks like this:
#CacheConfig(cacheNames = "property")
#Service
public class PropertyServiceImpl implements PropertyService {
private final PropertyRepository propertyRepository;
#Autowired
public PropertyServiceImpl(PropertyRepository propertyRepository) {
this.propertyRepository = propertyRepository;
}
#Override
public Property get(#NonNull String name, #Nullable String entity, #Nullable Long entityId) {
System.out.println("inside: " + name);
return propertyRepository.findByNameAndEntityAndEntityId(name, entity, entityId);
}
#Cacheable
#Override
public Property get(#NonNull String name) {
return get(name, null, null);
}
}
Now, when I call the RestController get endpoint and supply a value for the name, every request ends up doing inside the method that should be getting cached.
However, if I call the controller get endpoint but pass a hardcoded String into the service class method, like this:
#PostMapping("get")
public Property getPropertyByName(#RequestParam("name") String name) {
return propertyService.get("hardcoded");
}
Then the method is only invoked the first time, but not on subsequent calls.
What's going on here? Why is it not caching the method call when I supply a value dynamically?
Here is some configuration:
#Configuration
public class CacheConfiguration {
#Bean
public CacheManager cacheManager() {
val caffeineCacheManager = new CaffeineCacheManager("property", "another");
caffeineCacheManager.setCaffeine(caffeineCacheBuilder());
return caffeineCacheManager;
}
public Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.initialCapacity(200)
.maximumSize(500)
.weakKeys()
.recordStats();
}
}
2 solutions (they work for me):
remove .weakKeys()
propertyService.get(name.intern()) - wouldn't really do that, possibly a big cost
Sorry, but I don't have enough knowledge to explain this. Probably something to do with internal key representation by Caffeine.

Springboot #retryable not retrying

The following code is not retrying. What am I missing?
#EnableRetry
#SpringBootApplication
public class App implements CommandLineRunner
{
.........
.........
#Retryable()
ResponseEntity<String> authenticate(RestTemplate restTemplate, HttpEntity<MultiValueMap<String, String>> entity) throws Exception
{
System.out.println("try!");
throw new Exception();
//return restTemplate.exchange(auth_endpoint, HttpMethod.POST, entity, String.class);
}
I have added the following to the pom.xml.
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
I also tried providing different combinations of arguments to #Retryable.
#Retryable(maxAttempts=10,value=Exception.class,backoff=#Backoff(delay = 2000,multiplier=2))
Thanks.
In spring boot 2.0.2 Release, I have observed that the #Retryable is not working if you have retryable and called method in same class. On debugging found that the pointcut is not getting built properly. For now, the workaround for this problem is that we need to write the method in a different class and call it.
Working Example could be found here.
For the #Retryable annotation on the method to be discovered it needs to be called correctly from an initialised context. Is the method invoked from a bean from the spring context or called by other means?
If testing this is your runner using the SpringJunit4ClassRunner?
Spring's #Retryable, #Cacheable, #Transaction, etc. are ALL implemented using Aspect Oriented Programming. Spring implements AOP via proxy-based weaving. Proxies intercept calls from one bean to another. Proxies cannot intercept calls from one object's methods to another. This is a general limitation of proxy based weaving.
The following solutions address this limitation: 1) as mentioned above, use #Autowired (or #Resource) to inject a bean with a self reference; calls to this reference transit the proxy. 2) Use AspectJ's ClassLoader instead of Spring's default proxy-based weaving. 3) As mentioned above, place the methods on separate beans. I've done each in various situations, each has pros and cons.
I solved it. I figured out that if return something from the method that you trying to retry, then #Retryable() is not working.
maven dependency in pom.xml
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
Spring boot Application.java
#SpringBootApplication
#EnableTransactionManagement
#EnableRetry
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
in controller.java
#RestController
public class JavaAllDataTypeController {
#Autowired
JavaAllDataTypeService JavaAllDataTypeService;
#RequestMapping(
value = "/springReTryTest",
method = RequestMethod.GET
)
public ResponseEntity<String> springReTryTest() {
System.out.println("springReTryTest controller");
try {
JavaAllDataTypeService.springReTryTest();
} catch (Exception e) {
e.printStackTrace();
}
return new ResponseEntity<String>("abcd", HttpStatus.OK);
}
}
in service.java
#Service
#Transactional
public class JavaAllDataTypeService {
// try the method 9 times with 2 seconds delay.
#Retryable(maxAttempts=9,value=Exception.class,backoff=#Backoff(delay = 2000))
public void springReTryTest() throws Exception {
System.out.println("try!");
throw new Exception();
}
}
output: It' trying 9 times then throwing exception.
I had exactly the same issue as described in the original question.
In my case it turned out that the spring-boot-starter-aop dependency was accidentally not included. After adding it to my pom.xml, my #Retryable methods worked as expected.
Returning values from #Retryable methods works fine for me.
It work for return type as well
#Service
public class RetryService {
private int count = 0;
// try the method 9 times with 2 seconds delay.
#Retryable(maxAttempts = 9, value = Exception.class, backoff = #Backoff(delay = 2000))
public String springReTryTest() throws Exception {
count++;
System.out.println("try!");
if (count < 4)
throw new Exception();
else
return "bla";
}
}
For those who want to call #Retryable block in same class can to this way.
The key here is not to call the method directly and through self-injected bean
#Slf4j
#Service
public class RetryService {
#Resource(name = "retryService")
private RetryService self;
public String getValue(String appender) {
return self.getData(appender);
}
#Retryable(value = NumberFormatException.class, maxAttempts = 4, backoff = #Backoff(500))
public String getData(String appender) {
log.info("Calling getData");
Integer value = Integer.parseInt(appender);
value++;
return value.toString();
}
#Recover
public String recoverData(String appender) {
log.info("Calling recoverData");
return "DEFAULT";
}
}
Can read more about using Retry in detail here
An alternative could be RetryTemplate
#Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000l);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
and
retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
#Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});
worked out for me
source
Pretty old thread, but I wanted to share that after changing my method visibility from private to public, Retryable was successfully retrying.
This is in addition to using the self resource mentioned above.
Even I faced the same issue, Later after some investigation and research came to know that along with #Retryable annotation above the method we also need to provide #EnableRetry above the class. This #EnableRetry annotation either can be provided above same class in to which you have provided method you want to retry or above your main spring boot application class. For example like this:
#RequiredArgsConstructor
**#EnableRetry**
#Service
public class SomeService {
**#Retryable(value = { HttpServerErrorException.class, BadRequestException.class},
maxAttempts = maxRetry, backoff = #Backoff(random = true, delay = 1000,
maxDelay = 8000, multiplier = 2))**
public <T> T get( ) throws HttpServerErrorException, BadRequestException {
//write code here which you want to retry
}
}
I hope this will help and resolve your issue.
I got this one solved by moving #Retryable directly in front of the method I wanted to retry.
From this:
public class MyClass {
public String toBeRetried() {
return delegateTo();
}
#Retryable
public String delegateTo() {
throw new Exception();
}
}
To this:
public class MyClass {
#Retryable
public String toBeRetried() {
throw new Exception();
}
}

Categories