Return mono object from subscribe in restcontroller - spring

I have the following rest controller which reads data from redis and then needs to return some response
#RestController
#RequestMapping("/view")
public class ViewController {
#GetMapping(value = "/{channelId}/**")
public Mono<ResponseEntity<ViewResponse>> viewObject(#PathVariable(value = "channelId") String channelId) {
redisController.getChannelData(channelInfoset, channelId).subscribe(response -> {
if (response != null)
return Mono.just(new ResponseEntity<ViewResponse>(new ViewResponse("val", "1", false), HttpStatus.OK));
else return Mono.just(new ResponseEntity<ViewResponse>(null, HttpStatus.INTERNAL_SERVER_ERROR));
});
}}
This is the redis function to get data
public Mono<Object> getChannelData(String key, String value) {
return reactiveStringRedisTemplate.opsForHash().get(key,value);
}
How can I return the ViewResponse object after subscribe?

Comments from M. Deinum helped me out and was to resolve with
#RestController
#RequestMapping("/view")
public class ViewController {
#GetMapping(value = "/{channelId}/**")
public Mono<ResponseEntity<ViewResponse>> viewObject(#PathVariable(value = "channelId") String channelId) {
return redisController.getChannelData(channelInfoset, channelId).map(response -> {
if (response != null)
return Mono.just(new ResponseEntity<ViewResponse>(new ViewResponse("val", "1", false), HttpStatus.OK));
else return Mono.just(new ResponseEntity<ViewResponse>(null, HttpStatus.INTERNAL_SERVER_ERROR));
});
}}

Related

Spring boot how to return Json if findById returns null or doesn't find it?

I have an end point that returns json by its id and if it doesn't find it (id doesn't exist) then it should send a Json object saying not found. Here is my attempt.
PetController.java
#GetMapping("{id}")
public ResponseEntity<Pet> getPetById(#PathVariable("id") int petId) {
ResponseEntity<Pet> matchingPet = new ResponseEntity<Pet>(petService.getPetById(petId), HttpStatus.OK);
if(matchingPet != null) {
logger.info("pet found");
return matchingPet;
}
else {
logger.info("pet does not exist");
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
PetService.java
public interface PetService {
Pet getPetById(int petId);
}
PetServiceImpl.java
#Service
public class PetServiceImpl implements PetService {
private PetRepository petrepository;
public PetServiceImpl(PetRepository petrepository) {
this.petrepository = petrepository;
}
#Override
public Pet getPetById(int petId) {
Optional<Pet> pet = petrepository.findById(petId);
if (pet.isPresent()) {
return pet.get();
}
else {
return null;
//return new ResponseEntity<Pet>(HttpStatus.NOT_FOUND); I have tried
//this and it doesn't work either and still returns null without the notfound error.
}
}
}
It still only returns null. This seems like a logical error because I have a logger in my controller and even when I search a pet with an invalid id that does not exist, it still prints "pet found".
You are creating the instance in your Controller as follows ResponseEntity<Pet> matchingPet = new ResponseEntity<Pet>(petService.getPetById(petId), HttpStatus.OK);. Of course matchingPet will always be not null. Try the following:
#GetMapping("{id}")
public ResponseEntity<Pet> getPetById(#PathVariable("id") int petId) {
Pet matchingPet = petService.getPetById(petId);
if(matchingPet != null) {
logger.info("pet found");
return new ResponseEntity<Pet>(matchingPet, HttpStatus.OK);
}
else {
logger.info("pet does not exist");
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

Spring Webflux(Mono/Flux) with AOP triggering REST call at interception and working with Mono/Flux

I have written an #Aspect to intercept Reactive Methods that return values in Mono/Flux. Using #AfterReturning advice, i'm trying to fire an APNS notification by calling a webservice.
unfortunately the processNotification Mono services is immediately returning onComplete signal without executing the chain of calls. Below is my sample program.
#Aspect
#Component
#Slf4j
public class NotifyAspect{
private final NotificationServiceHelper notificationServiceHelper;
#Autowired
public NotifyAspect(NotificationServiceHelper notificationServiceHelper) {
this.notificationServiceHelper = notificationServiceHelper;
}
#AfterReturning(pointcut = "#annotation(com.cupid9.api.common.annotations.Notify)", returning = "returnValue")
public void generateNotification(JoinPoint joinPoint, Object returnValue) throws Throwable {
log.info("AfterReturning Advice - Intercepting Method : {}", joinPoint.getSignature().getName());
//Get Intercepted method details.
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//Get the Notification Details.
Notify myNotify = method.getAnnotation(Notify.class);
if (Mono.class.isAssignableFrom(returnValue.getClass())) {
Mono<Object> result = (Mono<Object>) returnValue;
result.doOnSubscribe(o -> {
log.debug("On Subscription...");
notificationServiceHelper.processNotification(myNotify.notificationType())
.doOnError(throwable -> {
log.error("Exception in notification processor",throwable);
});
});
}
}
}
#Slf4j
#Service
public class NotificationServiceHelper {
private ReactiveUserProfileRepository userProfileRepository;
#Value("${services.notification.url}")
private String notificationServiceUrl;
private RestWebClient restWebClient;
#Autowired
public NotificationServiceHelper(RestWebClient restWebClient,
ReactiveUserProfileRepository reactiveUserProfileRepository) {
this.restWebClient = restWebClient;
this.userProfileRepository = reactiveUserProfileRepository;
}
public Flux<Notification> processNotification(NotificationSchema.NotificationType notificationType) {
/*Get user profile details*/
return SessionHelper.getProfileId()
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 1!")))
.flatMap(profileId ->
Mono.zip(userProfileRepository.findByIdAndStatus(profileId, Status.Active), SessionHelper.getJwtToken()))
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 2!")))
.flatMapMany(tuple2 ->{
//Get user details and make sure there are some valid devices associated.
var userProfileSchema = tuple2.getT1();
log.info("Processing Notifications for User Profile : {}", userProfileSchema.getId());
if (Objects.isNull(userProfileSchema.getDevices()) || (userProfileSchema.getDevices().size() < 1)) {
return Flux.error(new InternalServerError("No Devices associate with this user. Can not send notifications."));
}
//Build Notification message from the Notification Type
var notificationsMap = new LinkedHashSet<Notification>();
userProfileSchema.getDevices().forEach(device -> {
var notificationPayload = Notification.builder()
.notificationType(notificationType)
.receiverDevice(device)
.receiverProfileRef(userProfileSchema.getId())
.build();
notificationsMap.add(notificationPayload);
});
//Get session token for authorization
var jwtToken = tuple2.getT2();
//Build the URI needed to make the rest call.
var uri = UriComponentsBuilder.fromUriString(notificationServiceUrl).build().toUri();
log.info("URI built String : {}", uri.toString());
//Build the Headers needed to make the rest call.
var headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.AUTHORIZATION, jwtToken);
var publishers = new ArrayList<Mono<ClientResponse>>();
notificationsMap.forEach(notification -> {
publishers.add(restWebClient.post(uri, headers, notification));
});
return Flux.merge(publishers).flatMap(clientResponse -> {
var httpStatus = clientResponse.statusCode();
log.info("NotificationService HTTP status code : {}", httpStatus.value());
if (httpStatus.is2xxSuccessful()) {
log.info("Successfully received response from Notification Service...");
return clientResponse.bodyToMono(Notification.class);
} else {
// return Flux.empty();
return clientResponse.bodyToMono(Error.class)
.flatMap(error -> {
log.error("Error calling Notification Service :{}", httpStatus.getReasonPhrase());
if (httpStatus.value() == 400) {
return Mono.error(new BadRequest(error.getMessage()));
}
return Mono.error(new InternalServerError(String.format("Error calling Notification Service : %s", error.getMessage())));
});
}
});
}).doOnError(throwable -> {
throw new InternalServerError(throwable.getMessage(), throwable);
});
}
}
How can we trigger this call in async without making the interception wait.. right now processNotification is always returning onComplete signal without executing. The chain is not executing as expected
#Target({ElementType.PARAMETER, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Log {
public String title() default "";
}
#SuppressWarnings({"unchecked"})
#Around("#annotation(operlog)")
public Mono<Result> doAround(ProceedingJoinPoint joinPoint, Log operlog) {
Mono<Result> mono;
try {
mono = (Mono<Result>) joinPoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
return mono.doOnNext(result -> {
//doSomething(result);
};
}

How to send a HTTP response in Zuul PRE_TYPE Filter

I want to prevent not logged user form accessing the proxy. I can throw an exception but the response is 404 instead of `401 or '403'. It it possible?
Filter code:
#Component
public class CustomZuulFilter extends ZuulFilter {
//FIXME - if 401,403 get the new token??, fallbackMethod = "fall",
#HystrixCommand(
commandProperties = {
#HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
#HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
}
)
#Override
public Object run() {
logger.debug("Adding zulu header");
String userName = getLoggedUser();
RequestContext ctx = RequestContext.getCurrentContext();
if (userName == null) {
// throw new RuntimeException("User not authenticated");
logger.info("User not authenticated");
ctx.setResponseStatusCode(401);
ctx.sendZuulResponse();
return null;
}
return null;
}
private String getLoggedUser() {
[...]
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public String filterType() {
return PRE_TYPE;
}
#Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
}
It might be a bit late, but i think you can remove ctx.sendZuulResponse();
and add ctx.setSendZuulResponse(false);

Adding a new field to body of the request from Netflix Zuul Pre-filter

I'm trying to add a new field to request's body, in a Zuul Pre-filter.
I'm using one of the Neflix's Zuul sample projects from here, and my filter's implementation is very similar to UppercaseRequestEntityFilter from this sample.
I was able to apply a transformation such as uppercase, or even to completely modify the request, the only inconvenient is that I'm not able to modify the content of body's request that has a length more than the original length of the body's request.
This is my filter's implementation:
#Component
public class MyRequestEntityFilter extends ZuulFilter {
public String filterType() {
return "pre";
}
public int filterOrder() {
return 10;
}
public boolean shouldFilter() {
RequestContext context = getCurrentContext();
return true;
}
public Object run() {
try {
RequestContext context = getCurrentContext();
InputStream in = (InputStream) context.get("requestEntity");
if (in == null) {
in = context.getRequest().getInputStream();
}
String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
body = body.replaceFirst("qqq", "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq");
// body = body.toUpperCase();
context.set("requestEntity", new ServletInputStreamWrapper(body.getBytes("UTF-8")));
}
catch (IOException e) {
rethrowRuntimeException(e);
}
return null;
}
}
This is the request that I'm doing:
This is the response that I'm receiving:
I was able to obtain what I wanted, using the implementation of PrefixRequestEntityFilter, from sample-zuul-examples:
#Component
public class MyRequestEntityFilter extends ZuulFilter {
public String filterType() {
return "pre";
}
public int filterOrder() {
return 10;
}
public boolean shouldFilter() {
RequestContext context = getCurrentContext();
return true;
}
public Object run() {
try {
RequestContext context = getCurrentContext();
InputStream in = (InputStream) context.get("requestEntity");
if (in == null) {
in = context.getRequest().getInputStream();
}
String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
body = body.replaceFirst("qqq", "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq");
byte[] bytes = body.getBytes("UTF-8");
context.setRequest(new HttpServletRequestWrapper(getCurrentContext().getRequest()) {
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStreamWrapper(bytes);
}
#Override
public int getContentLength() {
return bytes.length;
}
#Override
public long getContentLengthLong() {
return bytes.length;
}
});
}
catch (IOException e) {
rethrowRuntimeException(e);
}
return null;
}
}

A single instance of controller 'TestController' cannot be used to handle multiple requests

I have Some issues with the life time manager in unity, it uses the object like its singleton, but in the configuration I set it to "PerWebRequest".
The Error is:
A single instance of controller 'TestController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.
The PerWebRequest code:
public class UnityPerWebRequestLifetimeManager : LifetimeManager
{
private HttpContextBase _httpContext;
public UnityPerWebRequestLifetimeManager()
: this(new HttpContextWrapper(HttpContext.Current))
{
}
public UnityPerWebRequestLifetimeManager(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
private IDictionary<UnityPerWebRequestLifetimeManager, object> BackingStore
{
get
{
_httpContext = (HttpContext.Current != null) ? new HttpContextWrapper(HttpContext.Current) : _httpContext;
return UnityPerWebRequestLifetimeModule.GetInstances(_httpContext);
}
}
private object Value
{
[DebuggerStepThrough]
get
{
IDictionary<UnityPerWebRequestLifetimeManager, object> backingStore = BackingStore;
return backingStore.ContainsKey(this) ? backingStore[this] : null;
}
[DebuggerStepThrough]
set
{
IDictionary<UnityPerWebRequestLifetimeManager, object> backingStore = BackingStore;
if (backingStore.ContainsKey(this))
{
object oldValue = backingStore[this];
if (!ReferenceEquals(value, oldValue))
{
IDisposable disposable = oldValue as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
if (value == null)
{
backingStore.Remove(this);
}
else
{
backingStore[this] = value;
}
}
}
else
{
if (value != null)
{
backingStore.Add(this, value);
}
}
}
}
[DebuggerStepThrough]
public override object GetValue()
{
return Value;
}
[DebuggerStepThrough]
public override void SetValue(object newValue)
{
Value = newValue;
}
[DebuggerStepThrough]
public override void RemoveValue()
{
Value = null;
}
}
The controller:
public class TestController : Controller
{
//
// GET: /Test/
public TestController()
{
}
public ActionResult Index()
{
return View();
}
public ActionResult RadioButtonList()
{
return View("TestControl");
}
}
The Controller Factory:
public class ControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
return (controllerType == null) ? base.GetControllerInstance(requestContext, controllerType) : IoC.Resolve<IController>(controllerType);
}
}
And in one of the views I am trying to use it like this:
...
<% Html.RenderAction<TestController>(c => c.RadioButtonList()); %>
<% Html.RenderAction<TestController>(c => c.RadioButtonList()); %>
...
I don't know what wrong here?
Thanks.
Both unity controller requests are created within the same HTTP request/reply, hence you get the same instance. You need to switch so that the controllers have a Transient lifetime.
I would switch to DependencyResolver instead of using ControllerFactory since you are running MVC3.

Resources