How to use MongoDB $let in Spring Data MongoOperation - spring

MongoDB returns $count as an array of single element. And I want to transform it into an object(int or String) so I can map it directly to the String field in my class. For that I have used the projection below which do transform it into a int32 object. Now, I want to translate the below projection to Spring Data MongoOperation aggregate
{
$project:{
"count":
{
$let:{
"vars":{
"elem":
{ $arrayElemAt:["$count",0] }
},
"in":{
"count" :"$$elem.totalVideos"
}
}
}
}
}
Reading the documentation I built part of it as below.
Let.define(Let.ExpressionVariable.newVariable("elem").
forExpression(ArrayOperators.ArrayElemAt.arrayOf("count").elementAt(0)))
But not sure if I am doing it right. Does anybody know how this can be done in Spring Data?
Thanks in advance

Pretty implementation
Instead of "count" :"$$elem.totalVideos" we apply "count" : {"$toInt" : "$$elem.totalVideos"}
You may use ConvertOperators.ToString, ConvertOperators.ToLong, etc...
Aggregation.project()
.and(
Let.define(Let.ExpressionVariable
.newVariable("elem")
.forExpression(
ArrayOperators.ArrayElemAt.arrayOf("count").elementAt(0)
)
).andApply(PropertyExpression.property("count").definedAs(ToInt.toInt("$$elem.totalVideos")))
//.andApply(ToInt.toInt("$$elem.totalVideos"))
).as("count")
Less Pretty implementation
Aggregation.project()
.and(
Let.define(Let.ExpressionVariable
.newVariable("elem")
.forExpression(
ArrayOperators.ArrayElemAt.arrayOf("count").elementAt(0)
)
).andApply(new AggregationExpression() {
#Override
public Document toDocument(AggregationOperationContext context) {
return new Document("count", "$$elem.totalVideos");
}
})
).as("count")

Related

SpringBatch write to different entities

I have processed a "wrapperObject" (AimResponse in this case).
Depending on the property "type" I map to Document or SourceSpace object.
Then I need to persist these entities. I found an example similar to this one:
#Override
public void write(List<? extends List<AimResponse>> list)
throws Exception {
List<SourceSpace> sourceSpaces = new ArrayList<>();
List<Document> documents = new ArrayList<>();
for(List<AimResponse> item:list) {
for(AimResponse i:item) {
if(i.getType().indexOf("folder") >= 0) {
SourceSpace sourceSpace = Mapper.aimResponseToSourceSpace(i);
sourceSpace.setStatus(Status.FOUND.name());
sourceSpaces.add(sourceSpace);
} else if(i.getType().indexOf("document") >= 0) {
Document document = Mapper.aimResponseToDocument(i);
document.setStatus(Status.FOUND.name());
documents.add(document);
}
}
}
if(!CollectionUtils.isEmpty(sourceSpaces)) {
sourceSpaceWriter.write(sourceSpaces);
}
if(!CollectionUtils.isEmpty(documents)) {
documentWriter.write(documents);
}
}
In this example I'm not able to instantiate JdbcBatchItemWriter but anyway I think should be better if the processor could split into 2 different lists and call 2 different writers each one with its own type but I guess it's not possible.
Any help is appreciated.
ClassifierCompositeItemWriter is what you are looking for. It allows you to classify items according to a given criteria and call the corresponding writer.
In your case, you can classify items based on their type (i.getType()) and use a writer for each type. You can find an example of how to use that writer here.

java: how to limit score results in mongo

I have this mongo query (java):
TextQuery.queryText(textCriteria).sortByScore().limit(configuration.getSearchResultSize())
which performs a text search and sort by score.
I gave different wiehgt to different fields in the docuemnt, and now I'd like to retrieve only those results with score lower then 10.
is there a way to add that criteria to the query?
this didn't work:
query.addCriteria(Criteria.where("score").lt(10));
if the only way is to use aggregation - I need a mongoTemplate example for that.
in other words
how the do I translate the following mongo shell aggregate command, to java spring's mongoTemplate command??
can't find anywhere how to use the aggregate's match() API with the $text search component (the $text is indexed on several different fields):
db.text.aggregate(
[
{ $match: { $text: { $search: "read" } } },
{ $project: { title: 1, score: { $meta: "textScore" } } },
{ $match: { score: { $lt: 10.0 } } }
]
)
Thanks!
Please check with below code sample, MongoDB search with pagination code in java
BasicDBObject query = new BasicDBObject()
query.put(column_name, new BasicDBObject("$regex", searchString).append("$options", "i"));
DBCursor cursor = dbCollection.find(query);
cursor.skip((pageNum-1)*limit);
cursor.limit(limit);
Write a loop and and call the above code from loop and pass the values like pageNum starts from 1 to n and limit depends on your requirement. check the cursor is empty or not. If empty skip the loop if not continue calling the above code base.
Hope this will be helpful.

How to force Spring HATEOAS resources to render an empty embedded array?

I have the following controller method:
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, value = "session/{id}/exercises")
public ResponseEntity<Resources<Exercise>> exercises(#PathVariable("id") Long id) {
Optional<Session> opt = sessionRepository.findWithExercises(id);
Set<Exercise> exercises = Sets.newLinkedHashSet();
if (opt.isPresent()) {
exercises.addAll(opt.get().getExercises());
}
Link link = entityLinks.linkFor(Session.class)
.slash(id)
.slash(Constants.Rels.EXERCISES)
.withSelfRel();
return ResponseEntity.ok(new Resources<>(exercises, link));
}
So basically I am trying to get the expose a Set<> of Exercise entities for a particular Session. When the exercises entity is empty however I get a JSON representation like this:
{
"_links": {
"self": {
"href": "http://localhost:8080/api/sessions/2/exercises"
}
}
}
So basically there is no embedded entity, while something like the following would be preferrable:
{
"_links": {
"self": {
"href": "http://localhost:8080/api/sessions/2/exercises"
}
},
"_embedded": {
"exercises": []
}
}
any idea how to enforce this?
The problem here is that without additional effort there's no way to find out that the empty collection is a collection for Exercise. Spring HATEOAS has a helper class to work around this though:
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(Exercise.class);
Resources<Object> resources = new Resources<>(Arrays.asList(wrapper));
An EmbeddedWrapper allows you to explicitly mark objects to be added to the Resource or Resources as embedded, potentially even manually defining the rel they should be exposed under. As you can see above the helper also allows you to add an empty collection of a given type to the _embedded clause.
One can use the PagedResourceAssembler::toEmptyResource() method. For example, the following works:
Page<EWebProduct> products = elasticSearchTemplate.queryForPage(query, EWebProduct.class);
if(!products.hasContent()){
PagedResources pagedResources = pageAssembler.toEmptyResource(products, WebProductResource.class,baseLink);
return new ResponseEntity<PagedResources<WebProductResource>>(pagedResources, HttpStatus.OK);
}
I'd bet it works with other ResourceAssemblers as well.
If you have a Page< T >, you can convert it like this:
public static <T> PagedModel<EntityModel<T>> toModel(PagedResourcesAssembler<T> assembler,
Page<T> page) {
if (!page.isEmpty()) {
return assembler.toModel(page);
} else {
// toEmptyModel renders the _embedded field (with an empty array inside)
return (PagedModel<EntityModel<T>>) assembler.toEmptyModel(page, TenantSubscriptionResponseDto.class);
}
}
(You can obtain the PagedResourcesAssembler assembler by simply adding it as a parameter to the Controller method, and Spring will inject it).
Spring by default uses Jackson parser to serialize/deserialize json. As per http://wiki.fasterxml.com/JacksonFeaturesSerialization Jackson has a feature called WRITE_EMPTY_JSON_ARRAYS and its enabled by default. Maybe WRITE_EMPTY_JSON_ARRAYS is set to false in your config. please recheck your message converters configuration.

Spring Mongo mapping variable data

I'm using Spring Data MongoDB for my project. I work with a mongo database containing a lot of data, and I want to map this data within my Java application.
The problem I have is that some data back in time had a different structure.
For example sport_name is an array now, while in some old records is a String:
sport_name: "Soccer" // Old data
sport_name: [ // Most recent entries
{
"lang" : "en",
"val" : "Soccer"
},
{
"lang" : "de",
"val" : "Fussball"
}
]
Here is what I have until now:
#Document(collection = "matches")
public class MatchMongo {
#Id
private String id;
private ??? sport_name; // Best way?!
(What's the best way to)/(How would you) handle something like this?
If old data can be considered as "en" language, then separate structure can be used to store localized text:
class LocalName {
private String language;
private String text;
// getters/setters
}
So mapped object will store the collection of localized values:
public class MatchMongo {
// it can also be a map (language -> text),
// in this case we don't need additional structure
private List<LocalName> names;
}
This approach can be combined with custom converter, to use plain string as "en" locale value:
public class MatchReadConverter implements Converter<DBObject, MatchMongo> {
public Person convert(DBObject source) {
// check what kind of data located under "sport_name"
// and define it as "en" language text if it is an old plain text
// if "sport_name" is an array, then simply convert the values
}
}
Custom mapping is described here in details.
Probably you can write a utility class which will fetch all the data where sport_name is not an array and update the element sport_name to array. But this all depends on the amount of data you have.
You can use query {"sport_name":{$type:2}}, here 2 stands for String.
Refer for more details on $type: http://docs.mongodb.org/manual/reference/operator/query/type/

How can i dynamically Create Criteria Mongodb Spring data mongo Template

I need to dynamically create a criteria but i am having problem how can i build criteria dynamically.
I need exactly the same as in here Build dynamic queries with Spring Data MongoDB Criteria but i am getting an error while i am converting my Criteria list to a toArray as its keep saying that orCriteria does not have support for Criteria[]
here is my effort so far
Here is my query structure
{
"query":{
"where":[{
"or":[
{
"fieldName":"title","fieldValue":"Demo Event NEW YORK IIII22222",
"operator":"equal"
},
{
"fieldName":"createdBy","fieldValue":"system",
"operator":"equal"
}
]
}
]
}
}
and here is my parsing it to create criteria
if(null != eventSearch.getQuery())
{
if(null != eventSearch.getQuery().getWhere() && eventSearch.getQuery().getWhere().size()> 0)
{
for (Where whereClause : eventSearch.getQuery().getWhere()) {
if(null != whereClause.getOr() && whereClause.getOr().size() > 0){
for (Field field: whereClause.getOr()) {
if(field.getOperator().equalsIgnoreCase(QueryOperator.IS))
{
// So i need to append an or Condition to main query for each or object in my query can anyone tell me how can i achieve this?
query.addCriteria(Criteria.where(whereClause.getFieldName()).gte(whereClause.getFieldValue()));
}
}
}
}
}
I need to pass my all where clauses with in or object to orOperator function as a parameter
Criteria c = new Criteria().orOperator(Need to pass my where clauses here);
Better use an ArrayList of Criteria to keep $or criteria as below.
List<Criteria> orCriteriaList = new ArrayList<Criteria>();
for (Field field: whereClause.getOr()) {
if(field.getOperator().equalsIgnoreCase(QueryOperator.IS)){
Criteria c1 = Criteria.where(whereClause.getFieldName()).gte(whereClause.getFieldValue());
orCriteriaList.add(c1);
}
}
Then build the main query from this orCriteriaList as
mainQuery.addCriteria(new Criteria().orOperator(orCriteriaList.toArray(new Criteria[orCriteriaList.size()])));

Resources