Access URL Query Parameters in Lagom - java

How can I access URL query parameters from the http request in Lagom? I have a requirement where the set of query parameters are indefinite and infinite. I want to access the query parameter as a map. Is there any way to do that?

There isn't currently a way to access the query parameters as a map, or to declare a service call that takes indefinite parameters, as of Lagom 1.3.
In situations where the request may be of arbitrary length or complexity, it is better to encode request data in the entity body and use a request message deserializer in Lagom to map that to an immutable data type.

From the docs:
Query string parameters can also be extracted from the path, using a & separated list after a ? at the end of the path. For example, the following service call uses query string parameters to implement paging:
ServiceCall> getItems(long orderId, int pageNo, int pageSize);
default Descriptor descriptor() {
return named("orders").withCalls(
pathCall("/order/:orderId/items?pageNo&pageSize", this::getItems)
);
}
Check this link for more details.

https://github.com/msdhillon8989/lagom-demo-request-header.git
you can use the HeaderServiceCall of lagom.
#Override
public ServiceCall<NotUsed, String> method1() {
return readHeader(
new Function<String, ServerServiceCall<NotUsed, String>>() {
#Override
public ServerServiceCall<NotUsed, String> apply(String param) throws Exception {
return request -> {
return completedFuture(Utilities.ok(null, parseQueryString(param).toString()));
};
}
});
}
Definition of readHeader function is as below
public <Request, Response> ServerServiceCall<Request, Response> readHeader(Function<String, ServerServiceCall<Request, Response>> serviceCall) {
return HeaderServiceCall.composeAsync(new java.util.function.Function<RequestHeader, CompletionStage<? extends ServerServiceCall<Request, Response>>>() {
#Override
public CompletionStage<? extends ServerServiceCall<Request , Response>> apply(RequestHeader requestHeader) {
CompletableFuture<String> uri = CompletableFuture.supplyAsync(()->requestHeader.uri().getRawQuery().toString());
return uri.thenApply(query->
{
try {
return serviceCall.apply(query);
} catch (Exception e) {
e.printStackTrace();
throw new Forbidden("Bad request "+e.getMessage());
}
}
);
}
});
}

Related

Hilla Type 'Promise<void>' is missing the following properties from type

Trying to create an application in hilla(vaadin), which will list product and their details. For categorizing the product need to store the information related to category, but the issue facing while trying to list the category in a hilla/vaadin grid. From the endpoint returning a list of category and in the store consumed the list and try to display in the grid. While trying to run the application getting th error as "
Type 'Promise<void>' is missing the following properties from type
"
#Nonnull is specified in response of the list and in method still getting error
Below is the endpoint class
#Endpoint
#AnonymousAllowed
public class ProductEndpoint {
private InterProductService productService;
#Autowired
public ProductEndpoint(InterProductService productService) {
this.productService = productService;
}
public List<ProductDataList> fetchAllProducts() {
return this.productService.fetchProducts("");
}
public #Nonnull List<#Nonnull ProductCategoryDataList> fetchAllProdCategory() {
return this.productService.fetchProductCategory("");
}
#Nonnull
public EndpointResponse saveCatgeory(#Nonnull CategoryDetails categoryDetails) {
return this.productService.saveCategory(categoryDetails);
}
}
Below is the store
export class ProductStore {
constructor() {
makeAutoObservable(
this);
this.fetchProductCatgeory();
}
async saveProductCategory(prodCategory: CategoryDetails) {
const responseData = await ProductEndpoint.saveCatgeory(prodCategory);
return responseData;
}
async fetchProductCatgeory() {
const prodCatData = await ProductEndpoint.fetchAllProdCategory();
runInAction(() => {
return prodCatData;
});
}
}
Store class specific to that module of product and catgeory
export class CategoryListRegisterViewStore {
categoryList: ProductCategoryDataList[] = [];
constructor() {
makeAutoObservable(
this,
{
categoryList: observable.shallow,
}
);
this.loadProductCategory();
}
loadProductCategory() {
//return appStore.tricampCrmProductStore.fetchProductCatgeory();
const prodCategory = appStore.tricampCrmProductStore.fetchProductCatgeory();
runInAction(() => {
this.categoryList = prodCategory;
});
}
saveProductCategory(categoryDetails: CategoryDetails) {
return appStore.tricampCrmProductStore.saveProductCategory(categoryDetails);
}
}
export const categoryListRegisterViewStore = new CategoryListRegisterViewStore();
Html page with grid code is below
<vaadin-grid theme="row-stripes" .items="${categoryListRegisterViewStore.loadProductCategory}" >
<vaadin-grid-column header="Action" frozen-to-end auto-width flex-grow="0" ${columnBodyRenderer(this.actionRenderer,
[])}>
</vaadin-grid-column>
Tried multiple methods like returning from the method directly but getting different error like AllItem is not iterate. While inspect the promise also came "undefined". So maybe i did some mistake, expecting someone can support to fix the issue
There are multiple issues in your code:
The grid's items property is bound to the method for loading the categories. You should bind it to the categories loaded from that method, which should be stored in categoryListRegisterViewStore.categoryList:
<vaadin-grid theme="row-stripes" .items="${categoryListRegisterViewStore.categoryList}" >
The CategoryListRegisterViewStore.loadProductCategory method tries to assign the result of fetchProductCatgeory, which returns a Promise, to the categoryList property, which is of type ProductCategoryDataList[]. That's where you're getting the type error from. You should wait for the promise to resolve and then store the result of the promise:
async loadProductCategory() {
const prodCategory = await appStore.tricampCrmProductStore.fetchProductCatgeory();
runInAction(() => {
this.categoryList = prodCategory;
});
}
Finally, the fetchProductCatgeory method is flawed. It doesn't return the result of the endpoint call, because you wrapped the return into a runInAction. Instead, just directly return the promise from the endpoint call:
fetchProductCatgeory() {
return ProductEndpoint.fetchAllProdCategory();
}
At this point you might consider removing that method entirely and directly using the endpoint in loadProductCategory instead.

How to use Mono<Boolean> in if else conditional statement?

I am using Flux<Document> in reactive, so as to make my Rest Service reactive. I am returning ResponseEntity<Flux<Document>> as response to my rest service. Right now my service is always returning HttpStatus.ok(), but I want to enhance it to return HttpStatus.noContent() in case of no content is found.
To achieve this am trying to check the size of Flux. I figured out that this can be achieved either by .count() or .hasElements().
IF I consider .hasElements() then it returns Mono<Boolean>.
I am trying to understand as a newbie that how can I use this in making decisions between HttpStatus.ok() and HttpStatus.noContent().
Also is this the right way to use conditional statements in reactive or is there any other way to achieve it.
Request you to please help.
So here is what I did to accomplish the above ask:
final Flux<Document> returnDoc = <Reference of what I received from the Service layer>;
return returnDoc.hasElements()
.map(isNotEmpty -> {
if (isNotEmpty)
return ResponseEntity.ok().body(returnDoc);
else {
return ResponseEntity.noContent().build();
}
});
This worked for me. Let me know if there is any other solution which is better.
The trick with reactive conditionals is to utilize a couple of operators to achieve the if / else behaviour.
Some operators I learnt from using Webflux over the years:
filter(): filters the stream by some predicate (e.g. if (2 > 1))
map(): maps the response to some type if filter() emits true
flatMap(): maps the response to a Publisher (i.e. Mono/Flux)
switchIfEmpty(): emits a default Publisher if filter() emits false
defaultIfEmpty() emits a default type
I will share my redisson cache & r2dbc code as an example.
Here is our scenario in pseudocode:
If a key is found in cache
return value
Else
call database
set key & value pair in cache
return value
In either case, the value is wrapped into a ResponseEntity distinguished by status and the body.
#Override
public Mono<ResponseEntity<Res<UserSettings>>> getUserSetting(String username) {
Mono<UserSettings> queryAndSet =
userSettingsRepository
.findByUsername(username)
.flatMap(v1 -> cacheRepository.set("user_settings", username, v1).thenReturn(v1));
return cacheRepository
.get("user_settings", username, new TypeReference<UserSettings>() {}) // if then
.switchIfEmpty(queryAndSet) // else
.map(ResponseUtil::success) // if then
.defaultIfEmpty(singleError(UserMsg.USER_SETTINGS_NOT_FOUND.getCode())) // else
.map(v1 -> ResponseEntity.status(elseNotFound(v1)).body(v1)); // finally
}
CacheRepository interface specs:
public interface CacheRepository {
Mono<Void> set(String table, String key, Object value);
Mono<Void> set(String table, String key, Object value, Long ttl, TimeUnit timeUnit);
<T> Mono<T> get(String table, String key, TypeReference<T> type);
Mono<Boolean> delete(String table, String key);
}
ResponseUtil that helps with ResponseEntity wrapper:
public class ResponseUtil {
private ResponseUtil() {}
public static <T> Response<T> success(T data) {
return Response.<T>builder().success(true).data(data).build();
}
public static <T> Response<T> singleError(String error) {
return Response.<T>builder().success(false).errors(List.of(error)).build();
}
public static <T> Response<T> multipleErrors(List<String> errors) {
return Response.<T>builder().success(false).errors(errors).build();
}
public static HttpStatus elseBadRequest(Response<?> res) {
return Boolean.TRUE.equals(res.isSuccess()) ? HttpStatus.OK : HttpStatus.BAD_REQUEST;
}
public static HttpStatus elseNotFound(Response<?> res) {
return Boolean.TRUE.equals(res.isSuccess()) ? HttpStatus.OK : HttpStatus.NOT_FOUND;
}
}
And the Response:
// Idiotic wrapper
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Response<T> {
private T data;
private boolean success;
private List<String> errors;
}

In Hazelcast jet how can we store IList to normal list as I have to sent it in Response?

I am new to Hazelcast jet and in my application on data I am doing some aggregation and getting data but I want to send that in rest response so how can I change it to normal list?
public class ResponseMessage<T> {
private T responseClassType;
private ResponseMessage() {}
private ResponseMessage(T t) {
this.responseClassType = t;
}
public static <T> ResponseMessage<T> withResponseData(T classType) {
return new ResponseMessage<T>(classType);
}
public static ResponseMessage<Void> empty() {
return new ResponseMessage<>();
}
public T getResponseClassType() {
return responseClassType;
}
public void setResponseClassType(T responseClassType) {
this.responseClassType = responseClassType;
}
}
This is my generic response class and as below I am sending response after all calculations:
public ResponseMessage<?> runProcess(Pipeline pl) {
Map<String, BatchStage<Object>> allBatch = new HashMap<String,BatchStage<Object>>();
allBatch.put(z.get("id").toString(), new SomeCalulation().readSource(pipeline));
BatchStage<Object> h = allBatch.values().iterator().next();
h.writeTo(Sinks.list("abc"));
IList<Object> abc = jetInstance.getList("abc");
List<Object> result = new ArrayList(abc);
abc.destroy();
return ResponseMessage.withResponseData(result);
}
Now this is working but everytime I call rest request it is increasing the list and if I clear the list it is showing blank records, please help how can I convert it to normal list or best way to send response?
It was not working because I was joining it after method call:
runProcess(pl);
job.join(); // so because I am joining it after runProcess not working but if I directly return ResponseMessage.withResponseData(jetInstance.getList("abc")); and then join it will work.
I don't see submitting the pipeline as a job and waiting for the result (job.join()). I suppose you have omitted this from your code sample.
To solve your issue with empty list simply copy the result before destroying the list:
job.join();
IList<Object> abc = jetInstance.getList("abc");
List<Object> result = new ArrayList(abc)
abc.destroy();
return ResponseMessage.withResponseData(result);
Also, the list should have a unique name for each request, otherwise, multiple requests will write to the same list, having unpredictable results.

Override JAXRS #QueryParameter value using Post-matched Request Filter

I have a method wherein I need to override the values of the query parameter based on certain computations:
#GET
#Path("/channel")
#Produces(MediaType.APPLICATION_JSON)
#OptimizedDate
public FancountAndChannel computeFanCount(
#QueryParam("artist") String artistId,
#OptimizableDate #QueryParam("date") Integer date, //override this using filters
#QueryParam("channel") String channel) {
//do stuffs here
}
My filter looks like this:
#OptimizedDate
public class OptimizedDateFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context) throws IOException {
if (//certain condition) {
//im hoping this method would change the "date" value
Integer optimizedDate = //compute date here
updateDateParameter(context, "date", optimizedDate );
}
}
private void updateDateParameter(ContainerRequestContext context, String fieldName, Integer optimizedDate) {
//TODO: wont work, only for post-matching filters
URI oldUri = context.getUriInfo().getRequestUri();
URI newUri = null;
try {
newUri = new URIBuilder(oldUri).setParameter(fieldName, String.valueOf(optimizedDate)).build();
} catch (URISyntaxException e) {
e.printStackTrace();
}
LOGGER.debug("oldUri={}\nnewUri={}", oldUri, newUri);
context.setRequestUri(newUri);
//TODO: wont work: query parameters are immutable
//context.getUriInfo().getQueryParameters().putSingle(fieldName, String.valueOf(optimizedDate));
//context.getUriInfo().getQueryParameters().add(fieldName, Arrays.asList(String.valueOf(optimizedDate)));
}
I want to override the value, or set it (when its null). However, most of the methods I have tried are using immutable objects.
Is there any better way of doing this using filters? I may have to apply the same logic across a lot of methods, copy-pasting is not my style.
Summary of Requirements:
Value is to be computed and decided at runtime
Logic is to be applied across a variety of JAXRS endpoints ONLY
if you want only to set a value when its null than use #DefaultValue("default value you want")
public FancountAndChannel computeFanCount(
#QueryParam("artist") String artistId,
#OptimizableDate #DefaultValue("default value you want") #QueryParam("date") Integer date, //override this using filters
#QueryParam("channel") String channel) {
}
One workaround I found is
public class DelegationFilter implements ContainerRequestFilter {
#Context
private HttpServletRequest request;
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
final Map<String, String[]> parameterMap = request.getParameterMap();
parameterMap.put(fieldName, new String[] { String.valueOf(optimizedDate) });
}
}
This worked in liberty server. I hope it would work for you too.
Please note that this one updates the parametermap and not parameters. I dint find a way to update the parameters

How to resolve a promise inside another promise?

I have an action which requires to get a list of emails from a remote server. Then I want to use the emails to get a list of emailDomainInformation from another remote server (note that this second piece of info depends on the first). After all this, I want to output data from both servers onto a map and render it onto the page with dust.
I managed to get this to work without the second piece of data by doing it like this:
public static Result index()
{
F.Promise<Email> emailPromise = getEmailPromise(...);
F.Promise<Result> results = emailPromise.map( new F.Function<Email, Result>()
{
public Result apply(Email email)
{
Map<String, Object> data = new HashMap<String, Object>();
data.put("email", email.getAddress());
data.put("domain", email.getDomain());
dustRenderer.render(data);
}
}
async(results);
}
Now, since I want to make an async call to getEmailDomainData(email.getDomain()); inside the emailPromise.map() method. What do I do with the Promise<EmailDomain> object I get back? How do I put that into the data map to pass to the dustRenderer?
Here is an example that essentially does what you need:
public static Result social() {
final F.Promise<WS.Response> twitterPromise = WS.url("http://search.twitter.com/search.json").setQueryParameter("q", "playframework").get();
final F.Promise<WS.Response> githubPromise = WS.url("https://api.github.com/legacy/repos/search/playframework").get();
return async(
twitterPromise.flatMap(
new F.Function<WS.Response, F.Promise<Result>>() {
public F.Promise<Result> apply(final WS.Response twitterResponse) {
return githubPromise.map(
new F.Function<WS.Response, Result>() {
public Result apply(final WS.Response githubResponse) {
return ok(views.html.social.render(twitterResponse.asJson().findValuesAsText("text"), githubResponse.asJson().findValuesAsText("name")));
}
}
);
}
}
)
);
}
In this case the two run in parallel but you could move the second Promise creation into the handler for the first Promise.

Categories