I am looking for a way to filter out all objects from a given List of Object
after providing some conditions
.
For example
Class A
@Entity(value = "tbl_A")
public class A {
private String notes;
@Embedded
private List<SampleObject> sampleObject;
....getter and setter ...
}
Class B
@Embedded
public class SampleObject {
private boolean read;
private boolean sentByBot;
... getter and setter ...
}
Now, I only want to collect the SampleObject
which does have sentByBot
parameter set to true
. I am using following approach:
Query<A> queryForA = datastore.find(A.class);
queryForA.field("sampleObject.sentByBot").equal(false).retrievedFields(true, "sampleObject.sentByBot");
Above code is giving me the whole list of objects
which have sampleObject.sentByBot
true and false both.
I also tried filter
approach i.e.
queryForA.filter("sampleObject.sentByBot", false).retrievedFields(true, "sampleObject.sentByBot");
But no luck. Is there any way to get those fields only which have sampleObject.sentByBot
set true?
Edit
After implementing following code I got this:
Database Image
Code
AggregationOptions options = AggregationOptions.builder()
.outputMode(AggregationOptions.OutputMode.CURSOR)
.build();
//System.out.println(options.toString());
Projection filterProjection = Projection.projection(
"sampleObjects",
Projection.expression(
"$filter",
new BasicDBObject("input","$sampleObjects")
.append("cond",new BasicDBObject("$eq", Arrays.asList("$$this.sentByBot",true)))
)
);
AggregationPipeline pipeline = datastore.createAggregation(A.class)
.match(datastore.createQuery(A.class).filter("sampleObjects.sentByBot", true))
.project(
Projection.projection("fieldA"),
Projection.projection("fieldB"),
filterProjection
);
Iterator<A> cursor = pipeline.aggregate(A.class, options);
Output
"Command failed with error 28646: '$filter only supports an object as its argument'. The full response is { \"ok\" : 0.0, \"errmsg\" : \"$filter only supports an object as its argument\", \"code\" : 28646 }"
As stated earlier, what you want in order to only return "multiple" array elements which match a given condition is the $filter
aggregation pipeline operator in projection. In order to issue such an aggregation statement with Morphia you want something like this:
Projection filterProjection = projection(
"sampleObjects",
expression(
"$filter",
new BasicDBObject("input","$sampleObjects")
.append("cond",new BasicDBObject("$eq", Arrays.asList("$$this.sentByBot",true)))
)
);
AggregationPipeline pipeline = datastore.createAggregation(A.class)
.match(datastore.createQuery(A.class).filter("sampleObjects.sentByBot", true))
.project(
projection("fieldA"),
projection("fieldB"),
filterProjection
);
Which issues a pipeline to the server as:
[
{ "$match" : { "sampleObjects.sentByBot" : true } },
{ "$project" : {
"fieldA" : 1,
"fieldB" : 1,
"sampleObjects" : {
"$filter" : {
"input" : "$sampleObjects",
"cond" : { "$eq" : [ "$$this.sentByBot", true ] }
}
}
}}
]
And returns only array elements from the matching documents that also match the condition:
{
"className" : "com.snakier.example.A" ,
"_id" : { "$oid" : "5b0ce52c6a6bfa50084c53aa"} ,
"fieldA" : "something" ,
"fieldB" : "else" ,
"sampleObjects" : [
{ "name" : "one" , "read" : false , "sentByBot" : true} ,
{ "name" : "three" , "read" : true , "sentByBot" : true}
]
}
Note that you need to build the expression()
in argument manually from DBObject()
as there are no current "builders" supported in Morphia for this type of operation. It would be expected that future releases would change to the Document
interface which has been standard in the underlying Java driver for some time now.
As a full example listing:
package com.snakier.example;
import com.mongodb.AggregationOptions;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.aggregation.AggregationPipeline;
import org.mongodb.morphia.aggregation.Projection;
import org.mongodb.morphia.annotations.Embedded;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import static org.mongodb.morphia.aggregation.Projection.*;
public class Application {
public static void main(String[] args) {
final Morphia morphia = new Morphia();
morphia.mapPackage("com.snakier.example");
final Datastore datastore = morphia.createDatastore(new MongoClient(),"example");
// Clean example database
datastore.getDB().getCollection("example").drop();
// Create some data
final A first = new A("something","else");
final A second = new A("another","thing");
final SampleObject firstSample = new SampleObject("one", false, true);
final SampleObject secondSample = new SampleObject("two", false, false);
final SampleObject thirdSample = new SampleObject("three", true,true);
final SampleObject fourthSample = new SampleObject("four", true, false);
first.setSampleObjects(Arrays.asList(firstSample,secondSample,thirdSample));
datastore.save(first);
second.setSampleObjects(Arrays.asList(fourthSample));
datastore.save(second);
AggregationOptions options = AggregationOptions.builder()
.outputMode(AggregationOptions.OutputMode.CURSOR)
.build();
//System.out.println(options.toString());
Projection filterProjection = projection(
"sampleObjects",
expression(
"$filter",
new BasicDBObject("input","$sampleObjects")
.append("cond",new BasicDBObject("$eq", Arrays.asList("$$this.sentByBot",true)))
)
);
AggregationPipeline pipeline = datastore.createAggregation(A.class)
.match(datastore.createQuery(A.class).filter("sampleObjects.sentByBot", true))
.project(
projection("fieldA"),
projection("fieldB"),
filterProjection
);
Iterator<A> cursor = pipeline.aggregate(A.class, options);
while (cursor.hasNext()) {
System.out.println(morphia.toDBObject(cursor.next()));
}
}
}
@Entity(value = "example")
class A {
@Id
private ObjectId id;
private String fieldA;
private String fieldB;
@Embedded
private List<SampleObject> sampleObjects;
public A() {
}
public A(String fieldA, String fieldB) {
this.fieldA = fieldA;
this.fieldB = fieldB;
}
public void setSampleObjects(List<SampleObject> sampleObjects) {
this.sampleObjects = sampleObjects;
}
public List<SampleObject> getSampleObjects() {
return sampleObjects;
}
public ObjectId getId() {
return id;
}
public String getFieldA() {
return fieldA;
}
public void setFieldA(String fieldA) {
this.fieldA = fieldA;
}
public void setFieldB(String fieldB) {
this.fieldB = fieldB;
}
public String getFieldB() {
return fieldB;
}
}
@Embedded
class SampleObject {
private String name;
private boolean read;
private boolean sentByBot;
public SampleObject() {
}
public SampleObject(String name, boolean read, boolean sentByBot) {
this.name = name;
this.read = read;
this.sentByBot = sentByBot;
}
public String getName() {
return name;
}
public boolean isRead() {
return read;
}
public boolean isSentByBot() {
return sentByBot;
}
}