Is there any simple solution to build a directiry path string from a hierarchy with aggregation in SpringBoot and MongoDB?
Directory.java looks like
@Getter
@Setter
@EqualsAndHashCode
@Document
public class Directory {
@Id
private String id;
private String path;
private Date createdAt;
@DBRef
private Directory parentDirectory;
}
(I use lombok)
The code that I tried is this:
public String buildDirectoryHierarchy(Directory directory) {
GraphLookupOperation graphLookup = Aggregation.graphLookup("directory")
.startWith("$_id")
.connectFrom("parentDirectory")
.connectTo("_id")
.as("parents");
ProjectionOperation projectionOperation = Aggregation.project()
.andExpression("'$parents'").as("parents");
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("_id").is(directory.getId())),
graphLookup,
projectionOperation,
Aggregation.project()
.andExpression(
"{$reduce: { input: '$parents', initialValue: '', in: {$concat: ['$$value', '/', '$$this.path'] } } }")
.as("fullPath"));
AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, "directory", Map.class);
if (results.getMappedResults().isEmpty()) {
return "/";
}
return (String) results.getMappedResults().get(0).get("fullPath");
}
But I get the following exception:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed: org.springframework.expression.spel.SpelParseException:
Expression [{$reduce: { input: '$parents', initialValue: '', in: {$concat: ['$$value', '/', '$$this.path'] } } }] @73: EL1043E: Unexpected token. Expected 'rsquare(])' but was 'comma(,)'] with root cause
I'm not perfectly understand the code. Thanks for suggestions!
I created a working code:
public String buildDirectoryHierarchy(Directory directory) {
MatchOperation matchStage = Aggregation.match(Criteria.where("_id").is(directory.getId()));
GraphLookupOperation graphLookupStage = Aggregation.graphLookup("directory")
.startWith("$parentDirectory.$id")
.connectFrom("parentDirectory.$id")
.connectTo("_id")
.depthField("level")
.as("parents");
Sort sortByLevelDesc = Sort.by(Sort.Direction.DESC, "level");
SortArray sortArrayParents = SortArray.sortArray("$parents").by(sortByLevelDesc);
AddFieldsOperation addFieldsStage = Aggregation.addFields().addField("parents").withValue(sortArrayParents).build();
Cond reduceSeparatorCond = Cond.when(Eq.valueOf("$$value").equalToValue(""))
.then("")
.otherwise("/");
Concat reduceConcatExpr = Concat.valueOf("$$value")
.concatValueOf(reduceSeparatorCond)
.concat("$$this");
Reduce parentPathReduceExpr = Reduce.arrayOf("$parents.path")
.withInitialValue("")
.reduce(reduceConcatExpr);
ExpressionVariable parentPathVar = ExpressionVariable.newVariable("parentPathCalc").forExpression(parentPathReduceExpr);
ConditionalOperators.Cond finalSeparatorCond = Cond.when(Eq.valueOf("$$parentPathCalc").equalToValue(""))
.then("") // No separator if parent path is empty
.otherwise("/");
StringOperators.Concat finalConcatExpr = Concat.valueOf("$$parentPathCalc")
.concatValueOf(finalSeparatorCond)
.concatValueOf("$path");
Let letExpr = Let.define(parentPathVar).andApply(finalConcatExpr);
ProjectionOperation projectStage = Aggregation.project()
.andExclude("_id")
.and(letExpr).as("fullPath");
Aggregation aggregation = Aggregation.newAggregation(
matchStage,
graphLookupStage,
addFieldsStage,
projectStage
);
AggregationResults<FullPathResult> results = mongoTemplate.aggregate(
aggregation, "directory", FullPathResult.class
);
FullPathResult uniqueResult = results.getUniqueMappedResult();
return Optional.ofNullable(uniqueResult)
.map(FullPathResult::getFullPath)
.orElse(null);
}