Jest mapping with getSourceAsObjectList() - elasticsearch

I tried to map elastic data to list. since getSourceAsObjectList() method is deprecated, I made code like this.
class activityObj {
private String name;
private String oid;
public String getName()
{
return name;
}
public void setName( String name)
{
this.name = name;
}
public String getOid()
{
return oid;
}
public void setOid( String oid)
{
this.oid = oid;
}
}
List< Hit<activityObj, Void>> hits = result.getHits(activityObj.class);
List< activityObj> list = new ArrayList<activityObj>();
for (SearchResult.Hit<activityObj, Void> hit: hits) {
activityObj objectSource = hit.source; // null point error
list.add(objectSource);
logger.info( "hits : " + objectSource.getName());
}
It doesn't work as I expect and looks messy ( it's using two list value to map result). Am I doing wrong?

Michael's answer helped me and is a nice way to do it. Here's what I came up with including all the parts I used to make the query and get the result.
In my case I wanted to get a list of user ids collected from the source of each document in the result.
class DocSource {
private Integer userId;
public Integer getUserId() {
return userId;
}
}
String query = "{\n"
+ " \"_source\": [\n"
+ " \"userId\"\n"
+ " ],\n"
+ " \"query\": {\n"
+ " <some query config here>"
+ " }\n"
+ " }\n"
+ "}";
Search search = new Search.Builder(query)
.addIndex("my-index")
.build();
SearchResult result = jestClient.execute(search);
List<SearchResult.Hit<DocSource, Void>> hits = result.getHits(DocSource.class);
List<Integer> listOfIds = hits.stream()
.map(h -> h.source.getUserId())
.collect(Collectors.toList());

Have you tried something like this:
List< activityObj> list = hits.stream().map(h -> h.source).collect(Collectors.toList());

Related

Spring data projection for native query not mapped to interface

This my repostoriy for retrieve items
#Query(value = "SELECT DISTINCT M.ID as \"id\", "
+ " M.NAME_PRIMARY_LANG as \"name\" "
+ " FROM ECOMMERCE_CORE.MERCHANT_ITEMS M , "
+ " ECOMMERCE_CORE.PRODUCT_UNIT_OF_MEASURE P , "
+ " ECOMMERCE_CORE.LOOKUP_TYPES_STATUS S , "
+ " ECOMMERCE_CORE.ITEM_TYPES T , "
+ " ECOMMERCE_CORE.ITEM_PRICE I,"
+ " ECOMMERCE_CORE.MERCHANT_ITEM_BRAND B, "
+ " ECOMMERCE_CORE.MERCHANT_ITEM_CATEGORY C "
+ " WHERE M.ID = P.PRODUCT_ID AND M.ID=I.PRODUCT_ID AND M.ID = B.MERCHANT_ITEM_ID AND S.ID=M.STATUS_ID AND M.TYPE = T.ID AND M.MERCHANT_ID =?1 AND M.STATUS_ID =?2 "
+ " AND P.BRANCH_ID = ?3 AND I.CHANNEL_ID = ?4 ",
nativeQuery = true
)
List<ItemModelProjection> findBySupplierIdAndStatusCode(long id, long status, long branchId, long channelId, Pageable pageable);
and this my interface which i need to map the result to it
#Getter
#EqualsAndHashCode(of = {"id"})
public class ItemModelProjection {
private String id;
private String name;
public ItemModelProjection(final String id, final String name) {
this.id = id;
this.name = name;
}}
and the result of this query not mapped to the interface , what is the problem for it ?
You can solve this issue and achieve the result by using projections by making your DTO an interface with getters for columns returned by the query.
All you need to do is to have interface and contain query domains starting with get.
public interface ItemModelProjection {
Long getId();
String getName();
}
You need an interface if you want to retrieve those values. And be careful with the naming of the methods. If you have like in your case AS name then call the method getName(). But if you don't have AS specified and you are returning a value for example like PRODUCT_UNIT_OF_MEASURE then use the following method name: getProduct_Unit_Of_Measure().
For getting those two values use the following interface:
public interface ItemModelProjection {
String getId();
String getName();
}

Spring Web Client Not Binding JSON to POJO correctly?

Hi I'm using Spring Web Client to access Taste Dive REST API.
Here's the method I use to access the endpoint
public TasteDiveItemDto getReccs(Collection<Book> bookCollection) {
String parameters = "";
for(Book b : bookCollection)
parameters = parameters + "book:" + b.getTitle() + "%2C";
// remove the %2C at end of string
parameters = parameters.substring(0, parameters.length() - 3);
logger.info("Parameters = " + parameters);
// We want 2 reccs for every 1 book in the collection
int limit = bookCollection.size() * 2;
String req = "/similar" + "?q=" + parameters + "&" + "k=" + API_KEY + "&" + "limit=" + Integer.toString(limit);
logger.info("req = " + req);
logger.info("Accessing TasteDive API...");
return CLIENT.get().uri(req).retrieve().bodyToMono(TasteDiveItemDto.class).block();
}
And here are my POJOs
public class TasteDiveItemDto {
#JsonProperty("Similar")
private Similar similar;
public Similar getSimilar() {
return similar;
}
public void setSimilar(Similar similar) {
this.similar = similar;
}
#Override
public String toString() {
return "TasteDiveItemDto [similar=" + similar + "]";
}
}
public class Similar {
#JsonProperty("Info")
private Info[] info;
#JsonProperty("Results")
private Info[] results;
public Info[] getInfo() {
return info;
}
public void setInfo(Info[] info) {
this.info = info;
}
public Info[] getResults() {
return results;
}
public void setResults(Info[] results) {
this.results = results;
}
#Override
public String toString() {
return "Similar [info=" + Arrays.toString(info) + ", results=" + Arrays.toString(results) + "]";
}
}
public class Info {
#JsonProperty("Name")
private String name;
#JsonProperty("Type")
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#Override
public String toString() {
return "Info [name=" + name + ", type=" + type + "]";
}
}
Here's what I'm getting back from the endpoint when I use my toString method
TasteDiveItemDto
[
similar=
Similar
[
info=
[
Info
[
name=book:chemistry%2cbook:norwegian wood%2cbook:infinite jest%2cbook:flowers for algernon,
type=unknown
],
Info
[
name=book:chemistry%2cbook:norwegian wood%2cbook:infinite jest%2cbook:flowers for algernon,
type=unknown
]
],
results=[]
]
]
So what's essentially getting mapped are the parameters I put in the endpoint and not the correct JSON response. I've tried to use #JsonProperty on the methods and fields but to no luck.
I'm still a beginner at Spring and only read a bit of the Web Client documentation so I can't figure out entirely what I'm doing incorrectly. The strange thing is when I used Web Client to access Google Books API using the same methodology it worked out perfectly.
Please let me know if you need anymore information from my program. Thanks!

Conversion type error with native JPA query

I wrote a native query to get the top 5 tags for questions, I created a class to hold the returned data - which is only the tag name and the total number of occurrences, but I get a type conversion error that I don't understand.
#Query(value = "select distinct t.naam as name, count(t.id) as total from vragen_tags vt " +
"left join tags t on vt.tag_id = t.id " +
"left join vragen v on vt.vraag_id = v.id " +
"where v.actief = true " +
"group by t.naam " +
"order by total desc " +
"limit ?1", nativeQuery = true)
Set<TagAndCountResponse> getTopTags(int limit);
Class:
public class TagAndCountResponse {
private String name;
private int total;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
}
But I get this error:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [be.ambrassade.jeugdlink.model.response.TagAndCountResponse]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321) ~[spring-core-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194) ~[spring-core-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174) ~[spring-core-5.1.3.RELEASE.jar:5.1.3.RELEASE]
...
What is causing this error?
I found the answer through another medium. (kudos to Les) The solution is to use projections (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections), so in my case rewriting the concrete class to this interface makes it work.
public interface TagAndCountResponse {
String getName();
int getTotal();
}

spring-data-mongodb aggregation with composite ID

I'm having trouble reading documents from MongoDB using the aggregation framework: I always get null IDs in my results. This only happens for documents that have composite IDs. I tried various versions of spring-data-mongodb (1.10.12, 2.0.7), same result.
Entity definition class
#Document(collection="entities")
public class MyEntity {
static class CompositeKey implements Serializable {
private String stringKey;
private Integer intKey;
public CompositeKey(String stringKey, Integer intKey) {
this.stringKey = stringKey;
this.intKey = intKey;
}
public Integer getIntKey() {
return intKey;
}
public String getStringKey() {
return stringKey;
}
public String toString() {
return "{" + stringKey + " - " + intKey + "}";
}
}
#Id
private CompositeKey id;
private String param;
public MyEntity() {}
public MyEntity(String stringKey, Integer intKey) {
id = new CompositeKey(stringKey, intKey);
}
public CompositeKey getId(){
return id;
}
public void setId(CompositeKey id) {
this.id = id;
}
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
Testing code
public static void main(String[] args) {
MongoClient client = new MongoClient("127.0.0.1");
SimpleMongoDbFactory factory = new SimpleMongoDbFactory(client, "aggTest");
MongoTemplate mongoTemplate = new MongoTemplate(factory);
MyEntity entity = new MyEntity();
entity.setId(new MyEntity.CompositeKey("one", 1));
entity.setParam("param1");
mongoTemplate.save(entity);
entity = new MyEntity();
entity.setId(new MyEntity.CompositeKey("two", 2));
entity.setParam("param2");
mongoTemplate.save(entity);
Criteria crit = Criteria.where("param").ne("param3");
List<AggregationOperation> aggOpList = new ArrayList<AggregationOperation>();
aggOpList.add(Aggregation.match(crit));
System.out.println("Documents fetched with find: ");
for (MyEntity aggResult : mongoTemplate.find(new Query(crit), MyEntity.class).toArray(new MyEntity[0]))
System.out.println(aggResult.getId() + " - " + aggResult.getParam());
System.out.println("\nDocuments fetched with aggregate: ");
TypedAggregation<MyEntity> aggregation = new TypedAggregation<>(MyEntity.class, aggOpList);
AggregationResults<MyEntity> aggregate = mongoTemplate.aggregate(aggregation, MyEntity.class);
for (MyEntity aggResult : aggregate.getMappedResults())
System.out.println(aggResult.getId() + " - " + aggResult.getParam());
}
Output
Documents fetched with find:
{one - 1} - param1
{two - 2} - param2
Documents fetched with aggregate:
null - param1
null - param2
Debugging into the following method MappingMongoConverter.read(final MongoPersistentEntity entity, final Document bson, final ObjectPath path) I found that in the first case (find method) the documentAccessor variable has the following contents
Document{{_id=Document{{stringKey=one, intKey=1}}, param=param1, _class=MyEntity}}
whereas in the second case (aggregation query) it looks like
Document{{stringKey=one, intKey=1, param=param1, _class=MyEntity}}
The document gets flattened somehow, which makes it impossible for the converter to populate the ID field. I must be doing something wrong, but what?
Spring Data MongoDB lower than 3.x automatically flatten composite id (fields under composite id are unwrapped and place at root object). This is removed in version 3.0 onwards:
https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#new-features.3.0

How to use static constructor reference with a three parameters Java 8 Function (without make TriFunction)?

I am currently playing with Java 8 and I found a problem with Function. I would like ton know if there is a way to use function reference (name::methode) with a Function with tree parameters without declare a new functional interface (i.e. TriFunction).
I tried with currying way, but it doesn't work.
I have three classes :
Person.class
public class Person {
public enum Sex {
MALE, FEMALE
}
private String firstName;
private String lastName;
private Sex gender;
public Person(String firstName, String lastName, Sex gender) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Sex getGender() {
return gender;
}
}
PersonFactory
public class PersonFactory {
public static Person create(String firstName, String lastName, String gender) {
// Check firstName Parameter
if(firstName == null || firstName.isEmpty()) {
throw new IllegalArgumentException("The firstName argument expect to not be null or empty");
}
// Check lastName Parameter
if(lastName == null || lastName.isEmpty()) {
throw new IllegalArgumentException("The lastName argument expect to not be null or empty");
}
// Check gender Parameter
if(gender == null || gender.isEmpty()) {
throw new IllegalArgumentException("The gender argument expect to not be null or empty");
} else {
switch(gender) {
case "M":
return new Person(firstName, lastName, Sex.MALE);
case "F":
return new Person(firstName, lastName, Sex.FEMALE);
default:
throw new IllegalArgumentException("The gender parameter is supposed to be either 'M' for male or 'F' for Female");
}
}
}
}
CsVPersonParser
public class CsvPersonParser {
public Person parseLine(String line, String separator, Function<String, Function<String, Function<String, Person>>> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName).apply(lastName).apply(gender);
}
}
Here is my main class :
public class App {
public static void main(String[] args) {
final String IMAGINARY_CSV_FILE_LINE = "Jean,Dupont,M";
CsvPersonParser csvParser = new CsvPersonParser();
csvParser.parseLine("blabla", ",", PersonFactory::create);
}
}
The compilator show : The type PersonFactory does not define create(String) that is applicable here
It seems pretty logical. I have no solution. Is anyone can help me ?
I wonder why there is no way to do it simple without to create new things.
Probably a tri function is quite complex. I suggest that you use a builder to create a person.
The main reasons are, that you are not fixed on parameter ordering and you can extend your person. When you use a trifunction where all parameters are strings its often hard to say which parameter is the first/second/third. And when you want to add an address to a person it becomes more difficult to make it with generic classes like TriFunction.
My suggestion:
public interface PersonBuilder {
PersonBuilder withFirstName(String firstName);
PersonBuilder withLastName(String lastName);
PersonBuilder withGender(String gender);
Person create();
}
Concrete Implementation:
public class DefaultPersonBuilder implements PersonBuilder {
private String firstName;
private String lastName;
private String gender;
#Override
public PersonBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
#Override
public PersonBuilder withLastName(String lastName) {
this.lastName = lastName;
return this;
}
#Override
public PersonBuilder withGender(String gender) {
this.gender = gender;
return this;
}
#Override
public Person create() {
// Check firstName Parameter
if (firstName == null || firstName.isEmpty()) {
throw new IllegalArgumentException("The firstName argument expect to not be null or empty");
}
[... your implementation using the fields]
}
}
Your parser method:
public Person parseLine(String line, String separator, PersonBuilder person) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return person.withFirstName(firstName).withLastName(lastName).withGender(gender).create();
}
Now you can change the argument order oder add new fields to person without creating a function with 10 parameters. The parser interface is simpler now, too.
There is no way to do that what I wanted. However two other solutions is possible. Use a lambda instead of PersonFactory::create or create a new functional interface.
Here is the result :
New functional interface
#FunctionalInterface
public interface TriFunction<A, B, C, D> {
public D apply(A a, B b, C c);
}
Add a function parseLine with my new functional interface
public class CsvPersonParser {
// Currying style
public Person parseLine(String line, String separator, Function<String, Function<String, Function<String, Person>>> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName).apply(lastName).apply(gender);
}
// New Functionnal interface style
public Person parseLine(String line, String separator, TriFunction<String, String, String, Person> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName, lastName, gender);
}
}
My main class with solutions
public class App {
public static void main(String[] args) {
final String DATA_FILE_SEPARATOR = ",";
final String FAKE_CSV_LINE = "Jean,Dupont,M";
CsvPersonParser csvParser = new CsvPersonParser();
Person person;
// Use curryling style
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, f -> l -> g -> PersonFactory.create(f, l, g));
System.out.println("Currying style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
// Use new functionnal interface
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, PersonFactory::create);
System.out.println("TriFunction style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
// Use lambda style
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, (a,b,c) -> PersonFactory.create(a, b, c));
System.out.println("Lambda style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
}
}

Resources