-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1069 from AtlasOfLivingAustralia/feature/issue1067
Feature/issue1067
- Loading branch information
Showing
10 changed files
with
317 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
src/main/groovy/au/org/ala/ecodata/metadata/ExpressionUtil.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package au.org.ala.ecodata.metadata | ||
|
||
import groovy.util.logging.Slf4j | ||
import org.springframework.context.expression.MapAccessor | ||
import org.springframework.expression.AccessException | ||
import org.springframework.expression.EvaluationContext | ||
import org.springframework.expression.Expression | ||
import org.springframework.expression.TypedValue | ||
import org.springframework.expression.spel.support.StandardEvaluationContext | ||
import org.springframework.lang.Nullable | ||
import org.springframework.util.Assert | ||
|
||
@Slf4j | ||
class ExpressionUtil { | ||
|
||
|
||
/** Evaluates the supplied expression against the data in the supplied Map */ | ||
static def evaluateWithDefault(Expression expression, Map expressionContext, defaultValue) { | ||
StandardEvaluationContext context = new StandardEvaluationContext(expressionContext) | ||
context.addPropertyAccessor(new NoExceptionMapAccessor(null)) | ||
|
||
def result | ||
try { | ||
result = expression.getValue(context) | ||
if (Double.isNaN(result)) { | ||
result = defaultValue | ||
} | ||
} | ||
catch (Exception e) { | ||
log.debug("Error evaluating expression: ${expression.getExpressionString()}", e.getMessage()) | ||
result = defaultValue | ||
} | ||
result | ||
|
||
} | ||
|
||
|
||
/** | ||
* Extends the Spring MapAccessor but instead of throwing an Exception if the | ||
* Map does not have a property with the supplied name just returns null. | ||
*/ | ||
static class NoExceptionMapAccessor extends MapAccessor { | ||
|
||
TypedValue defaultValue | ||
NoExceptionMapAccessor(defaultValue) { | ||
this.defaultValue = new TypedValue(defaultValue) | ||
} | ||
@Override | ||
public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException { | ||
Assert.state(target instanceof Map, "Target must be of type Map"); | ||
Map<?, ?> map = (Map<?, ?>) target | ||
Object value = map.get(name) | ||
if (value == null && !map.containsKey(name)) { | ||
return defaultValue | ||
} | ||
return new TypedValue(value) | ||
|
||
} | ||
|
||
@Override | ||
public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException { | ||
return target instanceof Map | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
src/main/groovy/au/org/ala/ecodata/reporting/ExpressionAggregator.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package au.org.ala.ecodata.reporting | ||
|
||
import au.org.ala.ecodata.metadata.ExpressionUtil | ||
import org.springframework.expression.Expression | ||
import org.springframework.expression.ExpressionParser | ||
import org.springframework.expression.spel.standard.SpelExpressionParser | ||
|
||
/** | ||
* Categorises an activity into a group based on a supplied grouping criteria then delegates to the appropriate | ||
* Aggregator. | ||
*/ | ||
class ExpressionAggregator extends BaseAggregator { | ||
|
||
List<AggregatorIf> aggregators | ||
int count | ||
|
||
AggregatorFactory factory = new AggregatorFactory() | ||
Expression expression | ||
def defaultValue = 0 | ||
|
||
ExpressionAggregator(ExpressionAggregationConfig config) { | ||
|
||
ExpressionParser expressionParser = new SpelExpressionParser() | ||
expression = expressionParser.parseExpression(config.expression) | ||
defaultValue = config.defaultValue ?: 0 | ||
|
||
aggregators = config.childAggregations.collect { | ||
factory.createAggregator(it) | ||
} | ||
} | ||
|
||
PropertyAccessor getPropertyAccessor() { | ||
return null | ||
} | ||
|
||
void aggregateSingle(Map output) { | ||
count++ | ||
aggregators.each { | ||
it.aggregate(output) | ||
} | ||
|
||
} | ||
|
||
/** | ||
* If we have a single childAggregation, return a SingleResult, otherwise a | ||
* GroupedAggregationResult. | ||
*/ | ||
AggregationResult result() { | ||
|
||
SingleResult result = new SingleResult() | ||
Map expressionContext = [:] | ||
|
||
aggregators.each { | ||
AggregationResult childResult = it.result() | ||
if (childResult instanceof SingleResult) { | ||
expressionContext[childResult.label] = childResult.result | ||
} | ||
else if (childResult instanceof GroupedAggregationResult) { | ||
expressionContext[childResult.label] = childResult.groups?.collectEntries { | ||
[(it.group):it.results[0]?.result] | ||
} | ||
} | ||
} | ||
|
||
result.result = ExpressionUtil.evaluateWithDefault(expression, expressionContext, defaultValue) | ||
|
||
result | ||
} | ||
|
||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/test/groovy/au/org/ala/ecodata/reporting/DistinctSumAggregatorSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package au.org.ala.ecodata.reporting | ||
|
||
import spock.lang.Specification | ||
|
||
class DistinctSumAggregatorSpec extends Specification { | ||
|
||
// write a test for the DistinctSumAggregator | ||
def "The DistinctSumAggregator can sum the distinct values of a property"() { | ||
given: | ||
Map config = [ | ||
label:"value1", | ||
"property": "data.value1", | ||
"type": "DISTINCT_SUM", | ||
"keyProperty": "data.group" | ||
] | ||
Aggregators.DistinctSumAggregator aggregator = new AggregatorFactory().createAggregator(config) | ||
|
||
when: | ||
aggregator.aggregate([data:[value1:1, group: "group1"]]) | ||
aggregator.aggregate([data:[value1:1, group: "group1"]]) | ||
aggregator.aggregate([data:[value1:2, group: "group1"]]) | ||
aggregator.aggregate([data:[value1:2, group: "group2"]]) | ||
aggregator.aggregate([data:[value1:3, group: "group3"]]) | ||
|
||
AggregationResult result = aggregator.result() | ||
|
||
then: | ||
result.result == 6 | ||
} | ||
} |
Oops, something went wrong.