I have a Rest Endpoint
http://localhost:8080/api/invoice
I want to create another endpoint where I can get multiple ids like
http://localhost:8080/api/invoice?id[in]=1,2,3
How can I achieve it in Spring Boot?
With a standard request like http://localhost:8080/messages?id=1&id=2&id=3 it is easy.
You can get (multiple) ids via #RequestParam(value = "id") List<Integer> ids
example:
#RestController
class DemoController{
Map<Integer,String> messages = Map.of(1,"Hello 1", 2 , "Hello 2", 3, "Hello 3" , 4, "Hello 4", 5, "Hello 5" );
#GetMapping("/messages") // http://localhost:8080/api/invoice?id=1&id=2&id=3
List<String> messages(#RequestParam(value = "id") List<Integer> ids){
// do the lookup by ids...
return messages.entrySet().stream()
.filter( e -> ids.contains(e.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
}
Technically you could handle a RequestParam like id[in], but I would not recommend it.
First of all, a [ or ]-char are not allowed in a URL by default
You need to do extra work to retrieve the actual ids
application.properties : make sure tomcat allows [ + ]
server.tomcat.relaxed-query-chars=[,]
handle the request in your controller
#GetMapping("/messages") // => http://localhost:8080/messages?id[in]=1,2,3
List<String> messages(#RequestParam(value = "id[in]") String ids) {
// get the actual ids from the string
final List<Integer> idList =
Arrays.stream(ids.split(","))
.map(id -> Integer.parseInt(id))
.collect(Collectors.toList());
// do your lookup by ids...
return messages.entrySet().stream()
.filter( e -> idList.contains(e.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
Related
I have the following Java code:
var jwks = ((List<Object>) keys.getList()).stream()
.map(o -> new JsonObject((Map<String, Object>) o))
.collect(Collectors.toList());
and would like to translate safely into Kotlin code.
When I copy the code into Intellj, then it translates for me as follows:
val jwks = (keys.list as List<Any?>).stream()
.map { o: Any? -> JsonObject(o as Map<String?, Any?>?) }
.collect(Collectors.toList())
Can I do it better or should I let it as is.
Update
Maybe I have to provide more context. What I am trying to do is, to implement JWT Authorization for Vert.x with Keycloak in Kotlin regarding to the tutorial https://vertx.io/blog/jwt-authorization-for-vert-x-with-keycloak/.
I am trying to rewrite the method
private Future<Startup> setupJwtAuth(Startup startup) {
var jwtConfig = startup.config.getJsonObject("jwt");
var issuer = jwtConfig.getString("issuer");
var issuerUri = URI.create(issuer);
// derive JWKS uri from Keycloak issuer URI
var jwksUri = URI.create(jwtConfig.getString("jwksUri", String.format("%s://%s:%d%s",
issuerUri.getScheme(), issuerUri.getHost(), issuerUri.getPort(), issuerUri.getPath() + "/protocol/openid-connect/certs")));
var promise = Promise.<JWTAuth>promise();
// fetch JWKS from `/certs` endpoint
webClient.get(jwksUri.getPort(), jwksUri.getHost(), jwksUri.getPath())
.as(BodyCodec.jsonObject())
.send(ar -> {
if (!ar.succeeded()) {
startup.bootstrap.fail(String.format("Could not fetch JWKS from URI: %s", jwksUri));
return;
}
var response = ar.result();
var jwksResponse = response.body();
var keys = jwksResponse.getJsonArray("keys");
// Configure JWT validation options
var jwtOptions = new JWTOptions();
jwtOptions.setIssuer(issuer);
// extract JWKS from keys array
var jwks = ((List<Object>) keys.getList()).stream()
.map(o -> new JsonObject((Map<String, Object>) o))
.collect(Collectors.toList());
// configure JWTAuth
var jwtAuthOptions = new JWTAuthOptions();
jwtAuthOptions.setJwks(jwks);
jwtAuthOptions.setJWTOptions(jwtOptions);
jwtAuthOptions.setPermissionsClaimKey(jwtConfig.getString("permissionClaimsKey", "realm_access/roles"));
JWTAuth jwtAuth = JWTAuth.create(vertx, jwtAuthOptions);
promise.complete(jwtAuth);
});
return promise.future().compose(auth -> {
jwtAuth = auth;
return Future.succeededFuture(startup);
});
}
into Kotlin language.
You can use Kotlin's star projection which basically handles unknown generics in a type-safe way.
(keys.list as List<Map<*, *>>).map { JsonObject(it) }
Since there is no multi-mapping there is no need of Stream/Sequence API.
However, if you want to use lazy-evaluation (each element going through all mapping then next element in same way rather than mapping all element then running next map):
(keys.list as List<Map<*, *>>)
.asSequence()
.map { JsonObject(it) }
.map { /* Maybe some other mapping */ }
.filter { /* Maybe some filter */ }
.take(5) // Or some other intermediate operation
.toList() // Finally start the operations and collect
Edit: I forgot that the keys are String, so you can cast to List<Map<String, *>> instead :)
Firstly: I can't figure a proper title for this post.
Secondly: I have 2 lists and I am trying to merge/update some of the list properties base on one key.
List<MyClass> result = new ArrayList<MyClass>();
List<MyClass> intermediaryData = new ArrayList<MyClass>();
Both lists have the same T as MyClass which contains:
public String externalId; -- This is the unique id
public String companyName;
public String fiscalNumber;
public Integer noEmployees;
public Integer yearMonth;
List "result" contains:
externalId = "123"
companyName = "Demo"
List "intermediaryData" contains:
externalId = "123"
fiscalNumber= "84564213"
noEmployees = 12
yearMonth = 201812
Now, I can not figure a way to merge this 2 list base on "externalId" property so that I will have a final list with all properties set.
Eq:
externalId = "123"
companyName = "Demo"
fiscalNumber= "84564213"
noEmployees = 12
lastCloseYearMonth = 201812
I can make it with 2 fors, but is there a "faster" way using stream.
for (ClientPayrollDataSummaryDTO r : result)
for (ClientPayrollDataSummaryDTO rs : returnedSummaryList)
if (r.externalId.contains(rs.externalId)) {
r.fiscalNumber = rs.fiscalNumber;
r.lastCloseYearMonth = rs.lastCloseYearMonth;
....
}
You can use the following assuming an appropriate constructor :
List<MyClass> finalList = new ArrayList<>();
result.forEach(m1 -> intermediaryData.stream()
.filter(m2 -> m2.getExternalId().equals(m1.getExternalId()))
.map(m2 -> new MyClass(m1.getExternalId(), m1.getCompanyName(), m2.getFiscalNumber(), m2.getNoEmployees(), m2.getYearMonth()))
.forEach(finalList::add));
Or alternatively, could try and use the mergeFunction as
List<MyClass> finalMappedValues = new ArrayList<>(Stream.concat(result.stream(), intermediaryData.stream())
.collect(Collectors.toMap(MyClass::getExternalId, Function.identity(),
(a, b) -> new MyClass(a.getExternalId(), a.getCompanyName(), b.getFiscalNumber(), b.getNoEmployees(), b.getYearMonth())))
.values());
suppose I have these two lists
List<Person> persons = Arrays.asList(
new Person(1, "Mike", "Canada"),
new Person(2, "Jill", "England"),
new Person(3, "Will", "Whales"),
new Person(4, "Mary", "Spain"));
List<Metadata> metadata= Arrays.asList(
new metadata(1, "2000-01-01", "Naturalized", "Bachelor's of Arts"),
new metadata(2, "2001-01-01", "ExPat", "Masters of Chemestry"),
new metadata(3, "2017-05-01", "Citizen", "Buiness Management"),
new metadata(4, "2018-04-16", "Work Visa", "Nursing"));
where the end result is a new list:
List<PersonWithMetadata> personsAndMEtadata = Arrays.asList(
new PersonWithMetadata(1, "Mike", "Canada", "2000-01-01", "Naturalized", "Bachelor's of Arts"),
new PersonWithMetadata(2, "Jill", "England", "2001-01-01", "ExPat", "Masters of Chemestry"),
new PersonWithMetadata(3, "Will", "Whales", "2017-05-01", "Citizen", "Buiness Management"),
new PersonWithMetadata(4, "Mary", "Spain", "2018-04-16", "Work Visa", "Nursing"));
I am trying to find a Java streams way of combining the first two lists into the third--like an SQL join on the first input being an id number. It seems like there should be a way to do this, but I'm at a loss. how is this done? Also, suppose that there is at most one match between the two input lists.
YCF_L's solution should work, but it's an O(n2) solution. An O(n) solution could be achieved by converting one list to map from the id to the object, and then iterating over the other and getting the matching value from the map:
Map<Integer, Person> personMap =
persons.stream().collect(Collectors.toMap(Person::getId, Function.identity());
List<PersonWithMetadata> result =
metadata.stream()
.map(m -> new PersonWithMetadata(personMap.get(m.getId()), m)
.collect(Collectors.toList());
In the sample data the lists have matching objects in matching order. If this assumption is true for the real problem too, the solution could be must easier - you could stream the indexes and get the corresponding values from the lists:
List<PersonWithMetadata> result =
IntStream.reange(0, persons.size())
.map(i -> new PersonWithMetadata(persons.get(i), metadata.get(i))
.collect(Collectors.toList());
You can try this way :
List<PersonWithMetadata> personsAndMEtadata = persons.stream()
.map(p -> {
//search for the meta data based on the person id
Metadata meta = metadata.stream()
.filter(m -> m.getId() == p.getId())
.findFirst()
.get();
// then create a PersonWithMetadata object based on Person and metadata
return new PersonWithMetadata(
p.getId(), p.getFirstName(), p.getLastName(),
meta.getDate(), meta.getCity(), meta.getJob()
);
}
).collect(Collectors.toList());
About this line :
Metadata meta = metadata.stream().filter(m -> m.getId() == p.getId()).findFirst().get();
I assume that you have a meta data with the id of person, else you will get NullPointerException.
I believe what you're looking for is the zip function that was sadly omitted from the API.
The protonpack library provides it, which would allow you to zip and then map the tuple to the new structure.
StreamUtils.zip(persons, metadata, (person, metadata) -> ... )
The below example builds Map of Metadata objects using the ID as the key. This will help with performance as there's no need to iterate over the Metadata list for each Person in the List
Code
public static void main(String[] args) {
List<Person> persons = Arrays.asList(
new Person(1, "Mike", "Canada"),
new Person(2, "Jill", "England"),
new Person(3, "Will", "Whales"),
new Person(4, "Mary", "Spain"));
List<Metadata> metadataList = Arrays.asList(
new Metadata(1, "2000-01-01", "Naturalized", "Bachelor's of Arts"),
new Metadata(2, "2001-01-01", "ExPat", "Masters of Chemestry"),
new Metadata(3, "2017-05-01", "Citizen", "Buiness Management"),
new Metadata(4, "2018-04-16", "Work Visa", "Nursing"));
//Iterate over metadataList once and create map based on ID as key
Map<Integer, List<Metadata>> metadataMap = metadataList.stream()
.collect(Collectors.groupingBy(Metadata::getId));
//Iterate over personList and fetch metadata from Map to build PersonWithMetadata
List<PersonWithMetadata> personWithMetadataList = persons.stream().map(person -> {
List<Metadata> metadata = metadataMap.get(person.id);
if (metadata.isEmpty()) {
//TODO: Handle scenario for no metadata for person
}
//TODO: Build PersonWithMetadata
return new PersonWithMetadata();
}).collect(Collectors.toList());
}
I am new to Java8 syntax, How can we get the output as list after filtered.
In my case filter returns an array.
Added in comments, is there any better way can we get it
Config config = new Config("ooo", "wc", "code");
Config config1 = new Config("ppp", "wc", "code");
Config config2 = new Config("ooo", "wc", "code");
Config[] configs = {config, config1, config2};
Config config4 = new Config("ooo", "REG", "code");
Config config5 = new Config("ppp", "REG", "code");
Config config6 = new Config("ooo", "REG", "code");
Config[] _configs = {config4, config5, config6};
PromoCode promoCode = new PromoCode(121, "VOUCHER", "121", configs);
PromoCode promoCode1 = new PromoCode(122, "VOUCHER", "122", null);
PromoCode promoCode2 = new PromoCode(123, "LINK", "123", configs);
PromoCode promoCode3 = new PromoCode(124, "VOUCHER", "124", null);
PromoCode promoCode4 = new PromoCode(125, "LINK", "125", _configs);
PromoCode promoCode5 = new PromoCode(126, "LINK", "126", _configs);
List<String> resultantValues = new ArrayList<String>();
PromoCode[] promoCodes = {promoCode, promoCode1, promoCode2, promoCode3, promoCode4, promoCode5};
Stream<PromoCode> stream = Stream.of(promoCodes);
stream.parallel()
.filter(x -> x.getCode().equalsIgnoreCase("VOUCHER"))
.collect(Collectors.toList())
.parallelStream()
.forEach(x-> {
Stream.of(x.getConfigs())
.filter(t -> t.getOccasion().equals("wc"))
//after filter, how can we get the output
// List of list of strings format
.forEach(o -> {
resultantValues.add(o.getProduct()+"_"+o.getProduct());
});
});
System.out.println(resultantValues);
to retrieve a List<List<T>> it can be done as follows:
Stream.of(promoCodes)
.parallel() // is this really needed?
.filter(x -> x.getCode().equalsIgnoreCase("VOUCHER"))
.map(x->
Stream.of(x.getConfigs())
.filter(t -> t.getOccasion().equals("wc"))
.map(o -> o.getProduct()+"_"+o.getProduct())
.collect(Collectors.toList())
)
.collect(Collectors.toList());
or if you want a List<T> format then use flatMap:
Stream.of(promoCodes)
.parallel() // is this really needed?
.filter(x -> x.getCode().equalsIgnoreCase("VOUCHER"))
.flatMap(x->
Stream.of(x.getConfigs())
.filter(t -> t.getOccasion().equals("wc"))
.map(o -> o.getProduct()+"_"+o.getProduct())
)
.collect(Collectors.toList());
or as #Holger mentions, for the second approach you can avoid the nesting in the flatMap with:
Stream.of(promoCodes)
.parallel() // is this really needed?
.filter(x -> x.getCode().equalsIgnoreCase("VOUCHER"))
.flatMap(x -> Arrays.stream(x.getConfigs()))
.map(x -> x.getProduct() + "_" + x.getProduct())
.collect(Collectors.toList());
Which is definitely more readable:
Note that I've also removed some of the unnecessary method calls such as the intermediate collecting to a list .collect(Collectors.toList()) , .parallelStream() et al.
This should give you the desired results. First filter the promocodes with the given code VOUCHER. For each filtered promocode, you have an array of configs. We get that and flatten it to get a stream of Config objects. In the next step we filter out all the configs whose occasion is not equal to wc. Then we map all the matching config objects to get the desired result. At the final step we collect the result into a container.
final List<String> finalResult = Stream.of(promoCodes)
.filter(pc -> pc.getCode().equalsIgnoreCase("VOUCHER"))
.flatMap(pc -> Stream.of(pc.getConfigs()))
.filter(conf -> conf.getOccasion().equals("wc"))
.map(conf -> conf.getProduct() + "_" + conf.getProduct())
.collect(Collectors.toList());
I have a member function that will retrieve all membershipId of a member(one member might have multiples of membershipId).After retrieve all membershipId using List,it will call the url like this.
This is my service:
RestRequest request = RestRequest.newBuilder()
.url("/membership/" + membershipId + "/outlet")
.get();
This is my controller:
#RequestMapping(value = "/favouriteStores", method = RequestMethod.GET)
public Object FavouriteStores(ModelMap modelMap,HttpSession session)throws Exception {
String memberId = "5677a7075e3f1b998fc7483b";
List<Membership> membershipList= memberService.getMembershipByMemberId(memberId);
List<String> membershipIds = membershipList.stream().map(m->m.getId()).collect(Collectors.toList());
String membershipId = membershipIds.toString();
Set<Outlet> outletSet = membershipService.getOutletByMembershipId(membershipId);
My problem is it will transform the whole membershipId in one url like this
"membership/[12345, 54321]/outlet"
It should be two url like "membership/[12345]/outlet" and "membership/[54321]/outlet"
I know we can use foreach to do that in controller,but i don't know how.Thanks for any helps.
Try map method of Stream instead :
You can achieve this using map method of Stream.
Set<Outlet> outletSet = membershipIds.stream()
.map(membershipService::getOutletByMembershipId)
.collect(Collectors.toSet());
Even you can combine your previous stream operations and omit creation of intermediate list objects :
String memberId = "5677a7075e3f1b998fc7483b";
Set<Outlet> outletSet = memberService.getMembershipByMemberId(memberId)
.stream()
.map(Membership::getId)
.map(membershipService::getOutletByMembershipId)
.collect(Collectors.toSet())