Mutiny reactive - persist to DB if upstream is not null - reactive-programming

I working on a Quarkus + MongoDB Reactive+ Mutiny application. I have a Person object and Event Object. I am creating a new event for a person. My uri looks like this
POST /person/{personId}/event
I need to first check if the person exists in MongoDB. If the person exists then save event. If person does not exist then create a Error Status and return. I am tried everything but I am stuck and getting error that required return type is Uni but required type is Uni. I tried with transformToUni as well but it did not work. Also tried few other ways like onItemOrFailure() etc. but nothing seems to work.
Here's the full Code.
public class EventResource {
#Inject
EventRepository eventRepository;
#Inject
PersonRepository personRepository;
#POST
#Path("/{person_id}/event")
public Uni<Response> create(Event event, #PathParam("person_id") String personId){
//Check if personId exist.
Uni<Person> uniPerson = personRepository.getPersonById(personId);
//THIS WORKS BUT ON FAILURE IS TREATED WHEN ERROR IS RAISED FOR EeventRepository.craete() and not if person is not found.
/*return uniPerson.onItem().ifNotNull()
.transformToUni(pid -> eventRepository.create(event, pid.getId()))
.onItem().transform(e -> Response.ok().entity(e).build())
.onFailure()
.recoverWithItem(f-> {
AStatus status = createErrorStatus(f.getMessage());
return Response.serverError().entity(status).build();
});
*/
Uni<Response> eventResp = uniPerson.onItem().transform(person -> {
if(person==null)
return Response.serverError().build();
else{
return eventRepository.create(event, person.getId())
.onItem().transform(event1 -> Response.ok(event1).build());
}
});
return eventResp;
}

You can use mutiny ifNull:
#POST
#Path("/{person_id}/event")
public Uni<Response> create(Event event, #PathParam("person_id") String personId){
return personRepository
.getPersonById(personId)
.onItem().ifNotNull().transformToUni(person -> createEvent(event, person))
.onItem().ifNull().continueWith(this::personNotFound)
// This onFailure will catch all the errors
.onFailure()
.recoverWithItem(f-> {
AStatus status = createErrorStatus(f.getMessage());
return Response.serverError().entity(status).build();
});
}
private Uni<Response> createEvent(Event event, Person person) {
return eventRepository
.create(event, person.getId())
.map( e -> Response.ok().entity(e).status(CREATED).build())
}
private Response personNotFound() {
return Response.serverError().build();
}
The error you are seeing is because when the item is not null, you are returning a Uni<Uni<Response>>. This is one way to fix it:
Uni<Response> eventResp = uniPerson
.chain(person -> {
if (person==null)
return Uni.createFrom().item(Response.serverError().build());
else {
return eventRepository
.create(event, person.getId())
.map(event1 -> Response.ok(event1).build());
}
});
I'm using map and chain because they are shorter, but you can replace them with onItem().transform(...) and onItem().transformToUni(...).

Related

RxJava: how to do a second api call if first is successful and then create a combinded response

This is what I want to do:
call first rest API
if first succeeds call seconds rest API
if both are successful -> create an aggregated response
I'm using RxJava2 in Micronaut.
This is what I have but I'm not sure it's correct. What would happen if the first or second API call fails?
#Singleton
public class SomeService {
private final FirstRestApi firstRestApi;
private final SecondRestApi secondRestApi;
public SomeService(FirstRestApi firstRestApi, SecondRestApi secondRestApi) {
this.firstRestApi = firstRestApi;
this.secondRestApi = secondRestApi;
}
public Single<AggregatedResponse> login(String data) {
Single<FirstResponse> firstResponse = firstRestApi.call(data);
Single<SecondResponse> secondResponse = secondRestApi.call();
return firstResponse.zipWith(secondResponse, this::convertResponse);
}
private AggregatedResponse convertResponse(FirstResponse firstResponse, SecondResponse secondResponse) {
return AggregatedResponse
.builder()
.something1(firstResponse.getSomething1())
.something2(secondResponse.getSomething2())
.build();
}
}
This should be as simple as
public Single<AggregatedResponse> login(String data) {
return firstRestApi.call(data)
.flatMap((firstResponse) -> secondRestApi.call().map((secondResponse) -> {
return Pair.create(firstResponse, secondResponse);
})
.map((pair) -> {
return convertResponse(pair.getFirst(), pair.getSecond());
});
}
In which case you no longer need zipWith. Errors just go to error stream as usual.

Why is my Web API routing being re-routed / falsely routed?

I have two GET methods for a particular Controller / Repository:
public IEnumerable<InventoryItem> GetAllInventoryItems()
{
return inventoryItemsRepository.GetAll();
}
[Route("api/{controller}/{ID}/{CountToFetch}")]
public IEnumerable<InventoryItem> GetBatchOfInventoryItemsByStartingID(string ID, int CountToFetch)
{
return inventoryItemsRepository.Get(ID, CountToFetch);
}
Even though I've tried calling it all these ways from the client, with two arguments:
0)
formatargready_uri = string.Format("http://localhost:28642/api/inventoryItems/{0}/{1}", lastIDFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest)WebRequest.Create(formatargready_uri);
1)
formatargready_uri = string.Format("http://localhost:28642/api/inventoryItems/?ID={0}&CountToFetch={1}", lastIDFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest)WebRequest.Create(formatargready_uri);
2)
formatargready_uri = string.Format("http://localhost:28642/api/inventoryItems/ID={0}&CountToFetch={1}", lastIDFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest)WebRequest.Create(formatargready_uri);
...in each case it is still the first method (GetAll) that is being called. Why?
Here is my Repository code:
public IEnumerable<InventoryItem> GetAll()
{
return inventoryItems;
}
public IEnumerable<InventoryItem> Get(string ID, int CountToFetch)
{
return inventoryItems.Where(i => 0 < String.Compare(i.Id, ID)).Take(CountToFetch);
}
...and here is what's in WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApiWithParameters",
routeTemplate: "api/{controller}/{ID}/{CountToFetch}",
defaults: new { ID = RouteParameter.Optional, CountToFetch = RouteParameter.Optional }
);
}
UPDATE
This will show what I've tried as to routing calls to the Controller method I'm trying to have run:
//[HttpGet]
//[Route("api/{controller}/{ID:string}/{CountToFetch:int}")] <-- throws exception - won't run
//[Route("{controller}/{ID:string}/{CountToFetch:int}")] <-- throws exception - won't run
//[Route("inventoryItems/{ID:string}/{CountToFetch:int}")] <-- throws exception - won't run
//[Route("api/inventoryItems/{ID:string}/{CountToFetch:int}")] <-- throws exception - won't run
//[Route("api/{controller}/{ID}/{CountToFetch}")] // <-- runs, but is not called
[Route("api/InventoryItemsController/{ID}/{CountToFetch}")] // <-- runs, but is not called
//[Route("api/{controller}/{ID:string}/{CountToFetch:int}")] <-- throws exception - won't run
So the method that I don't want to be called is extremely robust: no matter how I decorate the other method, or how I call it, the undesired one runs.
UPDATE 2
Wouldn't it have been easier to just allow the calling of Controller methods by name? e.g, given this Controller method:
public IEnumerable<InventoryItem> GetBatchOfInventoryItemsByStartingID(string ID, int CountToFetch)
{
return inventoryItemsRepository.Get(ID, CountToFetch); //.Where(i => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}
...why [c,w]ouldn't it be called from the client like so:
formatargready_uri = string.Format("http://localhost:28642/api/InventoryItemsController.GetBatchOfInventoryItemsByStartingID/{0}/{1}", lastIDFetched, RECORDS_TO_FETCH);
???
ISTM that would be something that like that would have been a lot more intuitive.
UPDATE 3
So what it boils down to is: Why is my GetAll() method getting called, when it takes no args?
Possibly the routing mechanism is getting confused because of lastIDFetched being set to an empty string:
string lastIDFetched = string.Empty;
...and so then formatargready_uri. which is assigned to this way:
formatargready_uri = string.Format("http://locohost:28642/api/InventoryItems/{0}/{1}", lastIDFetched, RECORDS_TO_FETCH);
...is at first:
"http://locohost:28642/api/InventoryItems//100"
(when I would expect it to be:
"http://locohost:28642/api/InventoryItems/""/100"
)
Could it be that the "missing" first arg is what's throwing the routing mechanism off, so that when it sees:
"http://locohost:28642/api/InventoryItems//100"
...it doesn't know whether to call this:
public IEnumerable<InventoryItem> GetBatchOfInventoryItemsByStartingID(string ID, int CountToFetch)
{
return inventoryItemsRepository.Get(ID, CountToFetch); //.Where(i => string.Equals(p.Category, category,
StringComparison.OrdinalIgnoreCase));
}
...or this:
public IEnumerable<InventoryItem> GetAllInventoryItems()
{
return inventoryItemsRepository.GetAll();
}
???
UPDATE 4
When I comment out the other method, so that the client has no choice but to see the only existing method in the Controller/Repository, it does nothing on this line:
var webRequest = (HttpWebRequest)WebRequest.Create(formatargready_uri);
(the two-arg method in the Controller still isn't called)
This is all that's in the Controller now:
public class InventoryItemsController : ApiController
{
static readonly IInventoryItemRepository inventoryItemsRepository = new InventoryItemRepository();
[Route("api/InventoryItems/{ID}/{CountToFetch:int}")] // <-- with this route decoration commented out or not, makes no difference
public IEnumerable<InventoryItem> GetBatchOfInventoryItemsByStartingID(string ID, int CountToFetch)
{
return inventoryItemsRepository.Get(ID, CountToFetch); //.Where(i => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}
}
Here is the corresponding Repository interface:
interface IInventoryItemRepository
{
IEnumerable<InventoryItem> Get(string ID, int CountToFetch);
InventoryItem Add(InventoryItem item);
}
...Repository implementation:
public class InventoryItemRepository : IInventoryItemRepository
{
private readonly List<InventoryItem> inventoryItems = new List<InventoryItem>();
public InventoryItemRepository()
{
// code that populates inventoryItems by calling Add() not shown - it works, though
}
public IEnumerable<InventoryItem> Get(string ID, int CountToFetch)
{
return inventoryItems.Where(i => 0 < String.Compare(i.Id, ID)).Take(CountToFetch);
}
public InventoryItem Add(InventoryItem item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
inventoryItems.Add(item);
return item;
}
}
...and the client code that calls it:
formatargready_uri = string.Format("http://localhost:28642/api/InventoryItems/{0}/{1}", lastIDFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest)WebRequest.Create(formatargready_uri);
UPDATE 5
Well, I'll be darned like a holey sock. It seems to have been a problem with starting out with the empty string, after all. When I changed the initial value of lastIDFetched from string.Empty to "billy" it worked... Is this a bug? Is there a workaround? If I want to start from "scratch," what would I use rather than string.Empty? A blank space (" ") also doesn't work.
First, why are you having {controller} in the following attribute route? By decorating with an attribute route here you already are indicating the controller and action which should be hit, so remove {controller} here and replace it with the controller name.
[Route("api/{controller}/{ID}/{CountToFetch}")]
public IEnumerable<InventoryItem> GetBatchOfInventoryItemsByStartingID(string ID, int CountToFetch)
{
return inventoryItemsRepository.Get(ID, CountToFetch);
}
Requests 1) and 2) where you are using query string would not work because ID and CountToFetch are required route parameters.

Entity Framework + ODATA: side-stepping the pagination

The project I'm working on has the Entity Framework on top of an OData layer. The Odata layer has it's server side pagination turned to a value of 75. My reading on the subject leads me to believe that this pagination value is used across the board, rather than a per table basis. The table that I'm currently looking to extract all the data from is, of course, more than 75 rows. Using the entity framework, my code is simply thus:
public IQueryable<ProductColor> GetProductColors()
{
return db.ProductColors;
}
where db is the entity context. This is returning the first 75 records. I read something where I could append a parameter inlinecount set to allpages giving me the following code:
public IQueryable<ProductColor> GetProductColors()
{
return db.ProductColors.AddQueryOption("inlinecount","allpages");
}
However, this too returns 75 rows!
Can anyone shed light on how to truly get all the records regardless of the OData server-side pagination stuff?
important: I cannot remove the pagination or turn it off! It's extremely valuable in other scenarios where performance is a concern.
Update:
Through some more searching I've found an MSDN that describes how to do this task.
I'd love to be able to turn it into a full Generic method but, this was as close as I could get to a generic without using reflection:
public IQueryable<T> TakeAll<T>(QueryOperationResponse<T> qor)
{
var collection = new List<T>();
DataServiceQueryContinuation<T> next = null;
QueryOperationResponse<T> response = qor;
do
{
if (next != null)
{
response = db.Execute<T>(next) as QueryOperationResponse<T>;
}
foreach (var elem in response)
{
collection.Add(elem);
}
} while ((next = response.GetContinuation()) != null);
return collection.AsQueryable();
}
calling it like:
public IQueryable<ProductColor> GetProductColors()
{
QueryOperationResponse<ProductColor> response = db.ProductColors.Execute() as QueryOperationResponse<ProductColor>;
var productColors = this.TakeAll<ProductColor>(response);
return productColors.AsQueryable();
}
If unable turn off paging you'll receive 75 row by call, always. You can get all rows in following ways:
Add another IQueryable<ProductColor> AllProductColors and modify
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetEntitySetPageSize("ProductColors", 75); - Note only paged queries are present
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
You should call ProductColors as many as needed, for example
var cat = new NetflixCatalog(new Uri("http://odata.netflix.com/v1/Catalog/"));
var x = from t in cat.Titles
where t.ReleaseYear == 2009
select t;
var response = (QueryOperationResponse<Title>)((DataServiceQuery<Title>)x).Execute();
while (true)
{
foreach (Title title in response)
{
Console.WriteLine(title.Name);
}
var continuation = response.GetContinuation();
if (continuation == null)
{
break;
}
response = cat.Execute(continuation);
}
I use Rx with following code
public sealed class DataSequence<TEntry> : IObservable<TEntry>
{
private readonly DataServiceContext context;
private readonly Logger logger = LogManager.GetCurrentClassLogger();
private readonly IQueryable<TEntry> query;
public DataSequence(IQueryable<TEntry> query, DataServiceContext context)
{
this.query = query;
this.context = context;
}
public IDisposable Subscribe(IObserver<TEntry> observer)
{
QueryOperationResponse<TEntry> response;
try
{
response = (QueryOperationResponse<TEntry>)((DataServiceQuery<TEntry>)query).Execute();
if (response == null)
{
return Disposable.Empty;
}
}
catch (Exception ex)
{
logger.Error(ex);
return Disposable.Empty;
}
var initialState = new State
{
CanContinue = true,
Response = response
};
IObservable<TEntry> sequence = Observable.Generate(
initialState,
state => state.CanContinue,
MoveToNextState,
GetCurrentValue,
Scheduler.ThreadPool).Merge();
return new CompositeDisposable(initialState, sequence.Subscribe(observer));
}
private static IObservable<TEntry> GetCurrentValue(State state)
{
if (state.Response == null)
{
return Observable.Empty<TEntry>();
}
return state.Response.ToObservable();
}
private State MoveToNextState(State state)
{
DataServiceQueryContinuation<TEntry> continuation = state.Response.GetContinuation();
if (continuation == null)
{
state.CanContinue = false;
return state;
}
QueryOperationResponse<TEntry> response;
try
{
response = context.Execute(continuation);
}
catch (Exception)
{
state.CanContinue = false;
return state;
}
state.Response = response;
return state;
}
private sealed class State : IDisposable
{
public bool CanContinue { get; set; }
public QueryOperationResponse<TEntry> Response { get; set; }
public void Dispose()
{
CanContinue = false;
}
}
}
so for get any data thru OData, create a sequence and Rx does the rest
var sequence = new DataSequence<Product>(context.Products, context);
sequence.OnErrorResumeNext(Observable.Empty<Product>())
.ObserveOnDispatcher().SubscribeOn(Scheduler.NewThread).Subscribe(AddProduct, logger.Error);
The page size is set by the service author and can be set per entity set (but a service may choose to apply the same page size to all entity sets). There's no way to avoid it from the client (which is by design since it's a security feature).
The inlinecount option asks the server to include the total count of the results (just the number), it doesn't disable the paging.
From the client the only way to read all the data is to issue the request which will return the first page and it may contain a next link which you request to read the next page and so on until the last response doesn't have the next link.
If you're using the WCF Data Services client library it has support for continuations (the next link) and a simple sample can be found in this blog post (for example): http://blogs.msdn.com/b/phaniraj/archive/2010/04/25/server-driven-paging-with-wcf-data-services.aspx

Preserving model state with Post/Redirect/Get pattern

At the moment I am trying to implement the Post/Redirect/Get pattern with Spring MVC 3.1. What is the correct way to preserve and recover the model data + validation errors? I know that I can preserve the model and BindingResult with the RedirectAttributes in my POST method. But what is the correct way of recovering them in the GET method from the flash scope?
I have done the following to POST:
#RequestMapping(value = "/user/create", method = RequestMethod.POST)
public String doCreate(#ModelAttribute("user") #Valid User user, BindingResult result, RedirectAttributes rA){
if(result.hasErrors()){
rA.addFlashAttribute("result", result);
rA.addFlashAttribute("user", user);
return "redirect:/user";
}
return "redirect:/user/success";
}
And the following to GET the user creation form:
#RequestMapping(value = "/user", method = RequestMethod.GET)
public ModelAndView showUserForm(#ModelAttribute("user") User user, ModelAndView model){
model.addObject("user", user);
model.setViewName("userForm");
return model;
}
This allows me to preserve the given user data in the case of an error. But what is the correct way of recovering the errors?(BindingResult) I'd like to show them in the form with the spring form tags:
<form:errors path="*" />
In addition it would be interesting how to access the flash scope from the get method?
public class BindingHandlerInterceptor extends HandlerInterceptorAdapter {
public static final String BINDING_RESULT_FLUSH_ATTRIBUTE_KEY = BindingHandlerInterceptor.class.getName() + ".flashBindingResult";
private static final String METHOD_GET = "GET";
private static final String METHOD_POST = "POST";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if(METHOD_POST.equals(request.getMethod())) {
BindingResult bindingResult = getBindingResult(modelAndView);
FlashMap outFlash = RequestContextUtils.getOutputFlashMap(request);
if(bindingResult == null || ! bindingResult.hasErrors() || outFlash == null ) {
return;
}
outFlash.put(BINDING_RESULT_FLUSH_ATTRIBUTE_KEY, bindingResult);
}
Map<String, ?> inFlash = RequestContextUtils.getInputFlashMap(request);
if(METHOD_GET.equals(request.getMethod()) && inFlash != null && inFlash.containsKey(BINDING_RESULT_FLUSH_ATTRIBUTE_KEY)) {
BindingResult flashBindingResult = (BindingResult)inFlash.get(BINDING_RESULT_FLUSH_ATTRIBUTE_KEY);
if(flashBindingResult != null) {
BindingResult bindingResult = getBindingResult(modelAndView);
if(bindingResult == null) {
return;
}
bindingResult.addAllErrors(flashBindingResult);
}
}
}
public static BindingResult getBindingResult(ModelAndView modelAndView) {
if(modelAndView == null) {
return null;
}
for (Entry<String,?> key : modelAndView.getModel().entrySet()) {
if(key.getKey().startsWith(BindingResult.MODEL_KEY_PREFIX)) {
return (BindingResult)key.getValue();
}
}
return null;
}
}
Why don't you show the update form after the binding fails, so the user can try to resubmit the form?
The standard approach for this seems to be to return the update form view from the POST handler method.
if (bindingResult.hasErrors()) {
uiModel.addAttribute("user", user);
return "user/create";
}
You can then display errors with the form:errors tag.
what is the correct way of recovering them in the GET method from the
flash scope
I'm not sure I understand what you mean by recovering them. What you add as flash attributes before the redirect will be in the model after the redirect. There is nothing special that needs to be done for that. I gather you're trying to ask something else but I'm not sure what that is.
As phahn pointed out why do you redirect on error? The common way to handle this is to redirect on success.

Stripes : RedirectResolution; How can I redirect to specific action event?

I have an action bean in my stripes application. The default handler/method will display a list of data, a list of all my MarketResearch objects
On my JSP, I can click on one to view its details, this takes me to a different JSP with a pre-populated form based on the particular MarketResearch object that you selected.
I have another method on my action bean which is mapped to the save submit button, this takes in what is on the amended form, and persists it. After this has taken place, I want it to redirect back to the form, rather than to the listing (default handler) action, is this possible?
My action is as follows :
public class MarketResearchAction extends BaseAction
{
#SpringBean
ClientService clientService;
private static final String VIEW = "/jsp/marketResearch.jsp";
private Client client;
private Client clientBeforeChanges;
public Client getClient()
{
return client;
}
public void setClient(Client client)
{
this.client = client;
}
#DefaultHandler
public Resolution viewAll()
{
return new ForwardResolution(VIEW);
}
public Resolution viewClientMarketResearch()
{
if (client.getSector().equals("Education"))
{
return new ForwardResolution("/jsp/marketResearchEducation.jsp");
} else if (client.getSector().equals("Local Government"))
{
return new ForwardResolution("/jsp/marketResearchLocalGovernment.jsp");
} else if (client.getSector().equals("Housing Association"))
{
return new ForwardResolution("/jsp/marketResearchHousing.jsp");
}
return new ForwardResolution("/jsp/viewClientMarketResearch.jsp");
}
public Resolution save()
{
clientBeforeChanges = clientService.getClientById(client.getId());
clientService.persistClient(client);
getContext().getMessages().add(new SimpleMessage("{0} updated", client.getName()));
return new RedirectResolution("/MarketResearch.action").flash(this);
}
public Client getClientBeforeChanges()
{
return clientBeforeChanges;
}
public void setClientBeforeChanges(Client clientBeforeChanges)
{
this.clientBeforeChanges = clientBeforeChanges;
}
public ClientService getClientService()
{
return clientService;
}
public void setClientService(ClientService clientService)
{
this.clientService = clientService;
}
}
Is it possible? Or am I approaching the situation from a bad angle and should re-factor?
Thanks
Yes. You could return a RedirectResolution to the form jsp. If you're having difficulty with the parameters, if you have them in the save() method, you could do like so:
return new RedirectResolution("/theJsp.jsp")
.addParameter("one", one)
.addParameter("two", two)
.addParameter("three", three)
.flash(this);
If you don't have the params that were passed to the form, you'll have to keep them going somehow. You could pass the MarketResearch object through the form so you'd have it there.
<stripes:hidden name="marketResearch" value="${ActionBean.marketResearch}"/>
And add the requisite instance variable/getter/setter on your MarketResearchActionBean.