Permalink
Browse files

JSON Rules cleanup (improve code reuse and ensure location predicates…

… can be tracked using existing mechanism)
  • Loading branch information...
richturner committed Jan 7, 2019
1 parent 5289d9b commit 2b799bf2c969e5af9b8fac1b5242472408565695
@@ -83,7 +83,6 @@
import static org.openremote.model.query.BaseAssetQuery.Access.RESTRICTED_READ;
import static org.openremote.model.query.BaseAssetQuery.Include.ALL;
import static org.openremote.model.query.BaseAssetQuery.Include.ALL_EXCEPT_PATH_AND_ATTRIBUTES;
import static org.openremote.model.query.filter.LocationAttributePredicate.getLocationPredicate;
import static org.openremote.model.util.TextUtil.isNullOrEmpty;

public class AssetStorageService extends RouteBuilder implements ContainerService {
@@ -23,26 +23,28 @@
import com.vividsolutions.jts.geom.Envelope;
import org.geotools.referencing.GeodeticCalculator;
import org.openremote.container.timer.TimerService;
import org.openremote.model.attribute.AttributeType;
import org.openremote.manager.asset.AssetStorageService;
import org.openremote.model.attribute.Meta;
import org.openremote.model.attribute.MetaItem;
import org.openremote.model.geo.GeoJSONPoint;
import org.openremote.model.query.BaseAssetQuery;
import org.openremote.model.query.BaseAssetQuery.NumberType;
import org.openremote.model.query.NewAssetQuery;
import org.openremote.model.query.filter.*;
import org.openremote.model.rules.AssetState;
import org.openremote.model.rules.json.RuleCondition;
import org.openremote.model.rules.json.RuleOperator;
import org.openremote.model.util.Pair;
import org.openremote.model.util.TimeUtil;
import org.openremote.model.value.Value;
import org.openremote.model.value.Values;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.openremote.model.query.BaseAssetQuery.Operator.LESS_EQUALS;
import static org.openremote.model.query.BaseAssetQuery.Operator.LESS_THAN;
@@ -52,60 +54,91 @@
*/
public class AssetQueryPredicate implements Predicate<AssetState> {

final protected BaseAssetQuery query;
final protected NewAssetQuery query;
final protected TimerService timerService;
final protected AssetStorageService assetStorageService;

public AssetQueryPredicate(TimerService timerService, BaseAssetQuery query) {
// TODO: Remove this ctor once asset queries merged
public AssetQueryPredicate(TimerService timerService, AssetStorageService assetStorageService, BaseAssetQuery query) {
this.timerService = timerService;
this.assetStorageService = assetStorageService;
this.query = new NewAssetQuery();
this.query.ids = query.ids != null ? (String[])query.ids.toArray(new String[query.ids.size()]) : null;
this.query.names = query.name != null ? new StringPredicate[] {query.name} : null;
this.query.parents = query.parent != null ? new ParentPredicate[] {query.parent} : null;
this.query.paths = query.path != null ? new PathPredicate[] {query.path} : null;
this.query.attributes = query.attribute != null ? new RuleCondition<>(RuleOperator.AND, query.attribute, null) : null;
this.query.types = query.type != null ? new StringPredicate[] {query.type} : null;
this.query.userIds = query.userId != null ? new String[] {query.userId} : null;
this.query.tenant = query.tenant;
this.query.orderBy = query.orderBy;
this.query.limit = query.limit;
}

public AssetQueryPredicate(TimerService timerService, AssetStorageService assetStorageService, NewAssetQuery query) {
this.timerService = timerService;
this.assetStorageService = assetStorageService;
this.query = query;
}

@Override
public boolean test(AssetState assetState) {

if (query.ids != null && !query.ids.contains(assetState.getId()))
return false;

if (query.name != null && !asPredicate(query.name).test(assetState.getName()))
return false;

if (query.parent != null && !asPredicate(query.parent).test(assetState))
return false;
if (query.ids != null && query.ids.length > 0) {
if (Arrays.stream(query.ids).noneMatch(id -> assetState.getId().equals(id))) {
return false;
}
}

if (query.path != null && !asPredicate(query.path).test(assetState.getPath()))
return false;
if (query.names != null && query.names.length > 0) {
if (Arrays.stream(query.names)
.map(AssetQueryPredicate::asPredicate)
.noneMatch(np -> np.test(assetState.getName()))) {
return false;
}
}

if (query.tenant != null && !asPredicate(query.tenant).test(assetState))
return false;
if (query.parents != null && query.parents.length > 0) {
if (Arrays.stream(query.parents)
.map(AssetQueryPredicate::asPredicate)
.noneMatch(np -> np.test(assetState))) {
return false;
}
}

if (query.userId != null) {
// TODO Would require linked user IDs in AbstractAssetUpdate
throw new UnsupportedOperationException("Restriction by user ID not implemented in rules matching");
if (query.types != null && query.types.length > 0) {
if (Arrays.stream(query.types)
.map(AssetQueryPredicate::asPredicate)
.noneMatch(np -> np.test(assetState.getTypeString()))) {
return false;
}
}
if (query.type != null && !asPredicate(query.type).test(assetState.getTypeString()))
return false;

if (query.attribute != null) {
for (AttributePredicate p : query.attribute) {
if (!asPredicate(timerService::getCurrentTimeMillis, p).test(assetState))
return false;
if (query.paths != null && query.paths.length > 0) {
if (Arrays.stream(query.paths)
.map(AssetQueryPredicate::asPredicate)
.noneMatch(np -> np.test(assetState.getPath()))) {
return false;
}
}

if (query.attributeMeta != null) {
for (AttributeMetaPredicate p : query.attributeMeta) {
if (!asPredicate(timerService::getCurrentTimeMillis, p).test(assetState.getMeta())) {
return false;
}
if (query.tenant != null) {
if (!AssetQueryPredicate.asPredicate(query.tenant).test(assetState)) {
return false;
}
}

if (query.select != null) {
throw new UnsupportedOperationException("Projection with 'select' not supported in rules matching");
if (query.attributes != null) {
if (!asPredicate(timerService::getCurrentTimeMillis, query.attributes).test(assetState)) {
return false;
}
}

if (query.orderBy != null) {
throw new UnsupportedOperationException("Sorting with 'orderBy' not supported in rules matching");
// Apply user ID predicate last as it is the most expensive
if (query.userIds != null && query.userIds.length > 0) {
if (!assetStorageService.isUserAsset(Arrays.asList(query.userIds), assetState.getId())) {
return false;
}
}

return true;
@@ -283,12 +316,49 @@ public boolean test(AssetState assetState) {
}

public static Predicate<AssetState> asPredicate(Supplier<Long> currentMillisProducer, AttributePredicate predicate) {
return assetState -> {
if (predicate.name != null && !asPredicate(predicate.name).test(assetState.getAttributeName()))
return false;

return asPredicate(currentMillisProducer, predicate.value).test(assetState.getValue().orElse(null));
Predicate<String> namePredicate = predicate.name != null
? asPredicate(predicate.name) : str -> true;

Predicate<Value> valuePredicate = value -> {
if (predicate.value == null) {
return true;
}
return asPredicate(currentMillisProducer, predicate.value).test(value);
};

return assetState -> namePredicate.test(assetState.getAttributeName())
&& valuePredicate.test(assetState.getValue().orElse(null));
}

public static Predicate<AssetState> asPredicate(Supplier<Long> currentMillisProducer, NewAttributePredicate predicate) {

Predicate<AssetState> attributePredicate = asPredicate(currentMillisProducer, (AttributePredicate)predicate);

Predicate<Meta> metaPredicate = meta -> {

if (predicate.meta == null || predicate.meta.length == 0) {
return true;
}

for (AttributeMetaPredicate p : predicate.meta) {
if (!AssetQueryPredicate.asPredicate(currentMillisProducer, p).test(meta)) {
return false;
}
}
return true;
};

Predicate<Value> oldValuePredicate = value -> {
if (predicate.lastValue == null) {
return true;
}
return AssetQueryPredicate.asPredicate(currentMillisProducer, predicate.lastValue).test(value);
};

return assetState -> attributePredicate.test(assetState)
&& metaPredicate.test(assetState.getMeta())
&& oldValuePredicate.test(assetState.getOldValue().orElse(null));
}

public static Predicate<Value> asPredicate(Supplier<Long> currentMillisProducer, ValuePredicate predicate) {
@@ -357,6 +427,94 @@ public boolean test(AssetState assetState) {
return meta -> meta.stream().anyMatch(metaItemPredicate);
}

public static Predicate<RulesFacts> asPredicate(TimerService timerService, AssetStorageService assetStorageService, RuleCondition<NewAssetQuery> condition) {

if ((condition.predicates == null || condition.predicates.length == 0)
&& (condition.conditions == null || condition.conditions.length == 0)) {
return facts -> true;
}

RuleOperator operator = condition.operator == null ? RuleOperator.AND : condition.operator;

List<Predicate<RulesFacts>> assetPredicates = new ArrayList<>();

if (condition.predicates != null && condition.predicates.length > 0) {
assetPredicates.addAll(
Arrays.stream(condition.predicates)
.map(p ->
(Predicate<RulesFacts>) facts ->
facts.matchAssetState(p).findFirst().isPresent())
.collect(Collectors.toList())
);
}

if (condition.conditions != null && condition.conditions.length > 0) {
assetPredicates.addAll(
Arrays.stream(condition.conditions)
.map(c -> asPredicate(timerService, assetStorageService, c)).collect(Collectors.toList())
);
}

return asPredicate(assetPredicates, operator);
}

public static Predicate<AssetState> asPredicate(Supplier<Long> currentMillisProducer, RuleCondition<AttributePredicate> condition) {
if ((condition.predicates == null || condition.predicates.length == 0)
&& (condition.conditions == null || condition.conditions.length == 0)) {
return as -> true;
}

RuleOperator operator = condition.operator == null ? RuleOperator.AND : condition.operator;

List<Predicate<AssetState>> assetStatePredicates = new ArrayList<>();

if (condition.predicates != null && condition.predicates.length > 0) {
assetStatePredicates.addAll(
Arrays.stream(condition.predicates)
.map(p -> {
if (p instanceof NewAttributePredicate) {
return asPredicate(currentMillisProducer, (NewAttributePredicate)p);
}
return asPredicate(currentMillisProducer, p);
}).collect(Collectors.toList())
);
}

if (condition.conditions != null && condition.conditions.length > 0) {
assetStatePredicates.addAll(
Arrays.stream(condition.conditions)
.map(c -> asPredicate(currentMillisProducer, c)).collect(Collectors.toList())
);
}

return asPredicate(assetStatePredicates, operator);
}

protected static <T> Predicate<T> asPredicate(List<Predicate<T>> predicates, RuleOperator operator) {
return in -> {
boolean matched = false;

for (Predicate<T> p : predicates) {

if (p.test(in)) {
matched = true;

if (operator == RuleOperator.OR) {
break;
}
} else {
matched = false;

if (operator == RuleOperator.AND) {
break;
}
}
}

return matched;
};
}

public static Pair<Long, Long> asFromAndTo(long currentMillis, DateTimePredicate dateTimePredicate) {

Long from;
Oops, something went wrong.

0 comments on commit 2b799bf

Please sign in to comment.