$sort pipeline after $group not working using Spring aggregate class - spring

I have a User collection which looks like below sample :
User :{
"_id" : ObjectId("59f6dc660a975a3e3290ea01"),
"basicInfo" : {
"name" : "xxxx",
"age" : 27,
"gender" : "Male"
}
"otherInfo" {
"projects" : [
{
"_id" : ObjectId("59f6f9230a975a67cc7d7638"),
"name" : "Test Project",
"projectImage" : "images/project/59f6f9230a975a67cc7d7638.jpg",
"desc" : "This is a testing project",
"status" : "Active",
"verifyDet" : {
"method" : "Admin",
"status" : "PENDING",
"isVerified" : false
}
},
{
"_id" : ObjectId("59f6f9230a975a67cc7d5556"),
"name" : "Test Project Two",
"projectImage" : "images/project/59f6f9230a975a67cc7d5556.jpg",
"desc" : "This is a testing project",
"status" : "Closed",
"verifyDet" : {
"method" : "Admin",
"status" : "APPROVED",
"isVerified" : true
}
}
]
}
}
Note: One user can be part of multiple projects. But he needs approval from Admin to participate in the project activities. Verification is managed by verifyDet and projects are managed by projects array.
Actual requirement is to show the list of members in such a way that members having verification pending comes on top in alphabetic order and then approved/verified members in alphabetic order to Admin.
When I run below query on mongo shell I get list of Users with only one project detail(_id=59f6f9230a975a67cc7d7638) for which I want to search and result sorted by Verification pending users and User name. The result comes appropriately.
db.User.aggregate(
{$unwind:"$otherInfo.projects"},
{
$match:{
"otherInfo.projects._id":ObjectId("59f6f9230a975a67cc7d7638"),
"otherInfo.projects.status":"Active"
}
},
{$group: {_id: {"_id":"$_id", "basicInfo":"$basicInfo"}, "projects": {$push: "$otherInfo.projects"}}},
{$project:{"_id":"$_id._id", "basicInfo":"$_id.basicInfo", "otherInfo.projects":"$projects"}},
{$sort:{"otherInfo.projects.verifyDet.isVerified":1, "basicInfo.name":1}}
)
But when I create same aggregate in Spring like mentioned below I get exception:
public List<Map> fetchUsersList(String projectId, Pageable pageable) {
//unwind operation
AggregationOperation unwindOp = Aggregation.unwind("$otherInfo.projects");
Criteria criteria = Criteria.where("otherInfo.projects._id").is(new ObjectId(projectId));
criteria.and("otherInfo.projects.status").is("Active");
AggregationOperation matchOp = Aggregation.match(criteria);
AggregationOperation groupOp = Aggregation.group(
Fields.from(Fields.field("_id", "$_id")).and(Fields.field("basicInfo","$basicInfo"))).push("$otherInfo.projects").as("projects");
AggregationOperation projectOp = Aggregation.project(
Fields.from(Fields.field("_id","$_id._id"),
Fields.field("basicInfo","$_id.basicInfo"),
Fields.field("otherInfo.projects","$projects")));
AggregationOperation sortOp = Aggregation.sort(Direction.DESC, "otherInfo.projects.verifyDet.isVerified").and(Direction.DESC, "basicInfo.name");
Aggregation agg = Aggregation.newAggregation(unwindOp, matchOp, groupOp, projectOp, sortOp);
AggregationResults<User> results = mongoTemplate.aggregate(agg,
"User", User.class);
return results.getMappedResults();
}
Exception :
2017-12-15 19:24:31,852 ERROR GlobalExceptionHandler:75 - Exception Stack Trace :
java.lang.IllegalArgumentException: Invalid reference 'otherInfo.projects.verifyDet.isVerified'!
at org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext.getReference(ExposedFieldsAggregationOperationContext.java:99)
at org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext.getReference(ExposedFieldsAggregationOperationContext.java:80)
at org.springframework.data.mongodb.core.aggregation.SortOperation.toDBObject(SortOperation.java:73)
at org.springframework.data.mongodb.core.aggregation.AggregationOperationRenderer.toDBObject(AggregationOperationRenderer.java:56)
at org.springframework.data.mongodb.core.aggregation.Aggregation.toDbObject(Aggregation.java:580)
at org.springframework.data.mongodb.core.aggregation.Aggregation.toString(Aggregation.java:596)
at com.grpbk.gp.repository.impl.UserRepositoryCustomImpl.fetchUsersList(UserRepositoryCustomImpl.java:1128)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
Please let me know what I am doing wrong.

"otherInfo.projects.verifyDet.isVerified" field needs to be present in Project Oepration or group operation so that sort would get a reference for that.

Related

java 11 collect to map by keeping value unique

I have a specific json value as shown below,
{
"record_id" : "r01",
"teacherNstudents": [
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "steve",
"sid" : "S01"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "natasha",
"sid" : "S02"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "bruce",
"sid" : "S03"
}
},
{
"teacher" : {
"name" : "tony",
"tid" : "T01"
},
"student" : {
"name" : "victor",
"sid" : "S04"
}
},
{
"teacher" : {
"name" : "henry",
"tid" : "T02"
},
"student" : {
"name" : "jack",
"sid" : "S05"
}
},
{
"teacher" : {
"name" : "henry",
"tid" : "T02"
},
"student" : {
"name" : "robert",
"sid" : "S06"
}
}
]
}
I am trying to generate a map like the one below,
[ {"S01", "T01"} , {"S05","T02"} ]
This is by removing all duplicate values and selecting only one teacher and student. The current code I wrote for this is
var firstMap = records.getTeacherNstudents()
.stream()
.collect(Collectors.toMap(tS -> tS.getTeacher().getTid(),
tS -> tS.getStudent().getSid(),
(a1, a2) -> a1));
return firstMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
I believe this can be improved, by using Collectors.groupingBy. I am still working on it, but if anyone has any good idea on how to solve this, please share.
Using Java 8 groupingBy
You can try the below approach in order to have the Map<String,List<String>> or Map<String,Set<String>>(avoid duplicates) where key of map will be the teacher id and value as List or Set of Students corresponding to each teacher.
I have used groupingBy feature from java 8 and did the grouping based on the tId and before collecting it, I have downstream it to List or Set of student Ids corresponding to each tId.
Approach A: Map<String,Set< String >> (Uniques)
data.getTeacherStudentMappingList()
.stream()
.collect(Collectors.groupingBy(x -> x.getTeacher().getTid(), LinkedHashMap::new,
Collectors.mapping(y -> y.getStudent().getSid(),Collectors.toSet())));
Approach B : Map<String,List< String >> (Non-uniques, duplicates)
data.getTeacherStudentMappingList()
.stream()
.collect(Collectors.groupingBy(x -> x.getTeacher().getTid(), LinkedHashMap::new,
Collectors.mapping(y -> y.getStudent().getSid(),Collectors.toList())));
Here,
data is the converted object from the given json.
LinkedHashmap::new is used to preserve the order of student data from the json in the output.
collectors.mapping is used to convert the values corresponding to each key into the student ids.
Collectors.toList() will collect the list of student ids in the list.
Collectors.toSet() will collect the unique student ids in the set.
Output:
{T01=[S01, S02, S03, S04], T02=[S05, S06]}

Naming a Query Methods for MongoRepository with multiple conditions in SpringBoot

According to the documentation, I'm trying to get a working name for a method of a Spring MongoRepository that have to meet two different conditions. In plain words, the query sounds like: "look up for a determinate object by his id and it is one of mine or I want to share it"
The object:
{
"_id" : ObjectId("5c497..."),
"owner" : "myUserId",
"shared" : false,
}
The query:
db.getCollection('collection').find({$or : [
{ $and : [ { '_id' : ObjectId("5c497...") }, { 'owner' : 'myUserId' } ] },
{ $and : [ { '_id' : ObjectId("5c497...") }, { 'shared' : true } ] }
]})
I solved the problem with #Query
#Query("{\$or: [{\$and: [{'_id' : ?0}, {'owner' : ?1}]}, {\$and: [{'_id' : ?0}, {'shared' : true}]} ]}")
fun findIfAvailable(id: ObjectId, owner: String, shared: Boolean = true): Mono<MyObj>
But now, I wondered if it's possible to writing a method name for simplify the code ( and learn how use it ).
I tried without success findByIdAndOwnerOrShared, findByIdAndOwnerOrIdAndShared and so on
Thank you

Spring mongodb Find document if a single field matches in a list within a document

I have some data stored as
{
"_id" : ObjectId("abc"),
"_class" : "com.xxx.Team",
"name" : "Team 1",
"members" : [
{"userId" : 1, "email" : "a#x.com" },
{"userId" : 2, "email" : "b#x.com" },
]
}
{
"_id" : ObjectId("xyz"),
"_class" : "com.xxx.Team",
"name" : "Team 2",
"members" : [
{"userId" : 2, "email" : "b#x.com" },
{"userId" : 3, "email" : "c#x.com" }
]
}
I have 2 POJO classes Team (mapped to entire document),TeamMember (mapped to members inside a document).
Now I want to find to which team a specific user belongs to. For example if I search for a#x.com it should return me the document for Team 1. Similarly searching for b#x.com should return both of them as its in both the documents.
As I am very new to spring, not able to find out how to solve this.
Note: I am using MongoTemplate
somthing like this will do
final QueryBuilder queryBuilder = QueryBuilder.start();
//queryBuilder.and("members.email").is("a#x.com") This will work as well. try it out.
queryBuilder.and("members.email").in(Arrays.asList("a#x.com"))
final BasicDBObject projection = new BasicDBObject();
projection.put("fieldRequired", 1);
try (final DBCursor cursor = mongoTemplate.getCollection(collectionName).find(queryBuilder.get(), projection)
.batchSize(this.readBatchSize)) {
while (cursor.hasNext()) {
DBObject next = cursor.next();
........
// read the fields using next.get("field")
.........
}
}
batchsize and projection is not mandatory. Use projection if you don't want to fetch the whole document. You can specify which field in the document you want to fetch in the result.
You can use below code with the MongoTemplate
Query findQuery = new Query();
Criteria findCriteria = Criteria.where("members.email").is("b#x.com");
findQuery.addCriteria(findCriteria);
List<Team> teams = mongoTemplate.find(findQuery, Team.class);

Elastic search Update by Query to Update Complex Document

I have a use case of elastic search to update a doc.
My doc is something like this-
{
"first_name" : "firstName",
"last_name" : "lastName",
"version" : 1234,
"user_roles" : {
"version" : 12345,
"id" : 1234,
"name" : "role1"},
},
"groups" : {
"version" : 123,
"list": [
{"id":123, "name" : "ashd"},
{"id":1234, "name" : "awshd"},
]
}
}
Now depepeding on some feed I will either will be updating the parent doc or will be updating the nested doc.
I am able to find how to update the basic attributes like firstName and lastName but unable to get how to update complex/nested ones
I did something like from REST client-
"script": {
"inline": "ctx._source.user_roles = { "id" : 5678, "name" :"hcsdl"}
}
but its giving me exception-
Actual use case-
I will actually be getting a Map in java.
This key can be simple key like "first_name" or can be complex key like "user_role" and "groups"
I want to update the document using update by query on version.
The code I wrote is something like-
for (String key : document.keySet()) {
String value = defaultObjectMapper.writeValueAsString(document.get(key));
scriptBuilder.append("ctx._source.");
scriptBuilder.append(key);
scriptBuilder.append('=');
scriptBuilder.append(value);
scriptBuilder.append(";");
}
where document is the Map
Now I might get the simple fields to update or complex object.
I tried giving keys like user_roles.id and user_roles.name and also tried giving complete user_roles but nothing is working.
Can someone helpout
Try this with groovy maps instead of verbatim JSON inside your script:
"script": {
"inline": "ctx._source.user_roles = [ 'id' : 5678, 'name' : 'hcsdl']}
}

How to perform Sum on a Map Key in the Mongo DB document within Spring

My MongoDB document looks something like as following:
{
"_class" : "com.foo.foo.FooClass",
"_id" : ObjectId("5441948f3004e65fbda72d9c"),
"actionType" : "LOGIN",
"actor" : "bolt",
"extraDataMap" : {
"workHours" : NumberLong(11869)
},
}
Where extraDataMap is a HashMap stored from the java code. I have to get all the documents where "actionType" is "Login", group on "actor" and sum all the "workHours" for those individual actors
If I do below query on MongoDB directly it works:
db.activityLog.aggregate([
{$match : { actionType : "LOGIN" }},
{$group : { "_id" : "$actor", "hours" : { "$sum" : "$extraDataMap.workHours" } } },
{$sort : {_id : 1}}
]);
But If I run the query from Java Code
TypedAggregation<ActivityLog> agg = Aggregation.newAggregation(ActivityLog.class,
buildCriteria(),
group("actor").sum("extraDataMap.workHours").as("hours"),
sort(Sort.Direction.ASC, MongoActivityLogRepository.DOCUMENT_ID_FIELD_NAME)
);
AggregationResults<ActivityLog> result = mongoOperations.aggregate(agg, ActivityLog.class);
List<ActivityLog> results = result.getMappedResults();
It gives below error:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property work found for type java.lang.String
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:75)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:327)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:353)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:307)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:290)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:274)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:245)
at org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext.getReference(TypeBasedAggregationOperationContext.java:91)
at org.springframework.data.mongodb.core.aggregation.GroupOperation$Operation.getValue(GroupOperation.java:359)
at org.springframework.data.mongodb.core.aggregation.GroupOperation$Operation.toDBObject(GroupOperation.java:355)
at org.springframework.data.mongodb.core.aggregation.GroupOperation.toDBObject(GroupOperation.java:300)
at org.springframework.data.mongodb.core.aggregation.Aggregation.toDbObject(Aggregation.java:228)
at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:1287)
at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:1264)
at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:1253)
Really appreciate all the prompt responses :)
I had the same problem than you and I found this solution
Instead of using TypedAggregation, use a plain Aggregation. This way, spring data won't perform a type checking.
It would be as follows:
Aggregation agg = Aggregation.newAggregation(
buildCriteria(),
group("actor").sum("extraDataMap.workHours").as("hours"),
sort(Sort.Direction.ASC, MongoActivityLogRepository.DOCUMENT_ID_FIELD_NAME)
);
List<ActivityLog> results = mongoOperations.aggregate(agg, mongoOperations.getCollectionName(ActivityLog.class), ActivityLog.class).getMappedResults();
See that I used a different mongoOperations.aggregate signature, because since we are not using a TypedAggregation, we have to indicate over which collection we are performing the aggregation.
I hope this helps you.

Resources