[Major] Better generics support in Search API, includin ExpressionBuilder

This commit is contained in:
Robert von Burg 2018-03-14 08:09:29 +01:00
parent 131b4bdf83
commit 9bd0d43f50
8 changed files with 189 additions and 156 deletions

View File

@ -0,0 +1,55 @@
package li.strolch.search;
import java.util.Date;
import li.strolch.model.StrolchRootElement;
import li.strolch.utils.collections.DateRange;
public interface ExpressionBuilder {
Object extract(StrolchRootElement element);
default SearchExpression isEqualTo(Object right) {
return element -> PredicatesSupport.isEqualTo(right, false).matches(extract(element));
}
default SearchExpression isNotEqualTo(Object right) {
return element -> PredicatesSupport.isEqualTo(right, false).not().matches(extract(element));
}
default SearchExpression isEqualToIgnoreCase(Object right) {
return element -> PredicatesSupport.isEqualTo(right, true).matches(extract(element));
}
default SearchExpression isNotEqualToIgnoreCase(Object right) {
return element -> PredicatesSupport.isEqualTo(right, true).not().matches(extract(element));
}
default SearchExpression startsWith(Object right) {
return element -> PredicatesSupport.startsWith(right, false).matches(extract(element));
}
default SearchExpression startsWithIgnoreCase(Object right) {
return element -> PredicatesSupport.startsWith(right, true).matches(extract(element));
}
default SearchExpression endsWith(Object right) {
return element -> PredicatesSupport.endsWith(right, false).matches(extract(element));
}
default SearchExpression endsWithIgnoreCase(Object right) {
return element -> PredicatesSupport.endsWith(right, true).matches(extract(element));
}
default SearchExpression contains(Object right) {
return element -> PredicatesSupport.contains(right, false).matches(extract(element));
}
default SearchExpression containsIgnoreCase(Object right) {
return element -> PredicatesSupport.contains(right, true).matches(extract(element));
}
default SearchExpression inRange(DateRange range) {
return element -> range.contains((Date) extract(element));
}
}

View File

@ -0,0 +1,29 @@
package li.strolch.search;
import java.util.Date;
import li.strolch.utils.ObjectHelper;
import li.strolch.utils.collections.DateRange;
public class PredicatesSupport {
public static SearchPredicate isEqualTo(Object right, boolean ignoreCase) {
return left -> ObjectHelper.equals(left, right, ignoreCase);
}
public static SearchPredicate startsWith(Object right, boolean ignoreCase) {
return left -> ObjectHelper.startsWith(left, right, ignoreCase);
}
public static SearchPredicate endsWith(Object right, boolean ignoreCase) {
return left -> ObjectHelper.endsWith(left, right, ignoreCase);
}
public static SearchPredicate contains(Object right, boolean ignoreCase) {
return left -> ObjectHelper.contains(left, right, ignoreCase);
}
public static SearchPredicate inRange(DateRange range) {
return left -> range.contains((Date) left);
}
}

View File

@ -2,45 +2,70 @@ package li.strolch.search;
import li.strolch.model.Order;
import li.strolch.model.ParameterBag;
import li.strolch.model.StrolchElement;
import li.strolch.model.activity.Activity;
import li.strolch.model.parameter.Parameter;
public class SearchExpressions {
public interface SearchExpressions {
public static SearchExpression id(SearchPredicate predicate) {
default SearchExpression not(SearchExpression expression) {
return element -> !expression.matches(element);
}
default ExpressionBuilder id() {
return StrolchElement::getId;
}
default SearchExpression id(SearchPredicate predicate) {
return element -> predicate.matches(element.getId());
}
public static SearchExpression name(SearchPredicate predicate) {
default ExpressionBuilder name() {
return StrolchElement::getName;
}
default SearchExpression name(SearchPredicate predicate) {
return element -> predicate.matches(element.getName());
}
public static SearchExpression date(SearchPredicate predicate) {
default ExpressionBuilder date() {
return element -> ((Order) element).getDate();
}
default SearchExpression date(SearchPredicate predicate) {
return element -> predicate.matches(((Order) element).getDate());
}
public static SearchExpression state(SearchPredicate predicate) {
default ExpressionBuilder state() {
return element -> {
if (element instanceof Order)
return predicate.matches(((Order) element).getState());
return ((Order) element).getState();
if (element instanceof Activity)
return predicate.matches(((Activity) element).getState());
return ((Activity) element).getState();
throw new IllegalArgumentException(element.getObjectType() + " does not have a state!");
};
}
public static SearchExpression param(String bagId, String paramId, SearchPredicate predicate) {
default SearchExpression state(SearchPredicate predicate) {
return element -> predicate.matches(state().extract(element));
}
default ExpressionBuilder param(String bagId, String paramId) {
return element -> {
ParameterBag bag = element.getParameterBag(bagId);
if (bag == null)
return false;
return null;
Parameter<?> param = bag.getParameter(paramId);
return param != null && predicate.matches(param.getValue());
return param == null ? null : param.getValue();
};
}
public static SearchExpression paramNull(String bagId, String paramId) {
default SearchExpression param(String bagId, String paramId, SearchPredicate predicate) {
return element -> predicate.matches(param(bagId, paramId).extract(element));
}
default SearchExpression paramNull(String bagId, String paramId) {
return element -> {
ParameterBag bag = element.getParameterBag(bagId);
if (bag == null)

View File

@ -5,7 +5,7 @@ import java.util.stream.Stream;
import li.strolch.model.StrolchRootElement;
import li.strolch.persistence.api.StrolchTransaction;
public interface SearchNavigator {
public interface SearchNavigator<T extends StrolchRootElement> {
Stream<? extends StrolchRootElement> navigate(StrolchTransaction tx);
Stream<T> navigate(StrolchTransaction tx);
}

View File

@ -2,28 +2,51 @@ package li.strolch.search;
import java.util.Date;
import li.strolch.utils.ObjectHelper;
import li.strolch.utils.collections.DateRange;
public class SearchPredicates {
public interface SearchPredicates {
public static SearchPredicate isEqualTo(Object right, boolean ignoreCase) {
return left -> ObjectHelper.equals(left, right, ignoreCase);
default SearchPredicate isEqualTo(Object right) {
return PredicatesSupport.isEqualTo(right, false);
}
public static SearchPredicate startsWith(Object right, boolean ignoreCase) {
return left -> ObjectHelper.startsWith(left, right, ignoreCase);
default SearchPredicate isNotEqualTo(Object right) {
return PredicatesSupport.isEqualTo(right, false).not();
}
public static SearchPredicate endsWith(Object right, boolean ignoreCase) {
return left -> ObjectHelper.endsWith(left, right, ignoreCase);
default SearchPredicate isEqualToIgnoreCase(Object right) {
return PredicatesSupport.isEqualTo(right, true);
}
public static SearchPredicate contains(Object right, boolean ignoreCase) {
return left -> ObjectHelper.contains(left, right, ignoreCase);
default SearchPredicate isNotEqualToIgnoreCase(Object right) {
return PredicatesSupport.isEqualTo(right, true).not();
}
public static SearchPredicate inRange(DateRange range) {
default SearchPredicate startsWith(Object right) {
return PredicatesSupport.startsWith(right, false);
}
default SearchPredicate startsWithIgnoreCase(Object right) {
return PredicatesSupport.startsWith(right, true);
}
default SearchPredicate endsWith(Object right) {
return PredicatesSupport.endsWith(right, false);
}
default SearchPredicate endsWithIgnoreCase(Object right) {
return PredicatesSupport.endsWith(right, true);
}
default SearchPredicate contains(Object right) {
return PredicatesSupport.contains(right, false);
}
default SearchPredicate containsIgnoreCase(Object right) {
return PredicatesSupport.contains(right, true);
}
default SearchPredicate inRange(DateRange range) {
return left -> range.contains((Date) left);
}
}

View File

@ -8,9 +8,6 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import li.strolch.model.Order;
import li.strolch.model.Resource;
import li.strolch.model.activity.Activity;
import li.strolch.utils.collections.Paging;
public class SearchResult<T> {
@ -25,18 +22,6 @@ public class SearchResult<T> {
return this.stream;
}
public RootElementSearchResult<Resource> asResources() {
return new RootElementSearchResult<>(this.stream.map(e -> (Resource) e));
}
public RootElementSearchResult<Order> asOrders() {
return new RootElementSearchResult<>(this.stream.map(e -> (Order) e));
}
public RootElementSearchResult<Activity> asActivities() {
return new RootElementSearchResult<>(this.stream.map(e -> (Activity) e));
}
public <U> SearchResult<U> map(Function<T, U> mapper) {
return new SearchResult<U>(this.stream.map(mapper));
}

View File

@ -9,15 +9,15 @@ import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.privilege.model.Restrictable;
import li.strolch.utils.collections.DateRange;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.ExceptionHelper;
public abstract class StrolchSearch implements Restrictable {
public abstract class StrolchSearch<T extends StrolchRootElement>
implements SearchExpressions, SearchPredicates, Restrictable {
private String privilegeValue;
private SearchNavigator navigator;
private SearchNavigator<T> navigator;
private SearchExpression expression;
public StrolchSearch() {
@ -35,7 +35,7 @@ public abstract class StrolchSearch implements Restrictable {
return this;
}
public final RootElementSearchResult<StrolchRootElement> search(StrolchTransaction tx) {
public RootElementSearchResult<T> search(StrolchTransaction tx) {
try {
PrivilegeContext privilegeContext = tx.getContainer().getPrivilegeHandler().validate(tx.getCertificate());
privilegeContext.validateAction(this);
@ -45,42 +45,37 @@ public abstract class StrolchSearch implements Restrictable {
}
// first prepare
prepare();
define();
// then validate status
DBC.PRE.assertNotNull("navigation not set! Call one of resources(), orders() or activities()", this.navigator);
DBC.PRE.assertNotNull("expression not set! Call where()!", this.expression);
@SuppressWarnings("unchecked")
Stream<StrolchRootElement> stream = (Stream<StrolchRootElement>) this.navigator.navigate(tx)
.filter(e -> this.expression.matches(e));
return new RootElementSearchResult<StrolchRootElement>(stream);
Stream<T> stream = this.navigator.navigate(tx).filter(e -> this.expression.matches(e));
return new RootElementSearchResult<T>(stream);
}
protected abstract StrolchSearch prepare();
protected abstract void define();
// Navigator
protected final StrolchSearch resources(String... types) {
this.navigator = tx -> tx.streamResources(types);
@SuppressWarnings("unchecked")
protected StrolchSearch resources(String... types) {
this.navigator = tx -> (Stream<T>) tx.streamResources(types);
return this;
}
protected final StrolchSearch orders(String... types) {
this.navigator = tx -> tx.streamOrders(types);
@SuppressWarnings("unchecked")
protected StrolchSearch orders(String... types) {
this.navigator = tx -> (Stream<T>) tx.streamOrders(types);
return this;
}
protected final StrolchSearch activities(String... types) {
this.navigator = tx -> tx.streamActivities(types);
@SuppressWarnings("unchecked")
protected StrolchSearch activities(String... types) {
this.navigator = tx -> (Stream<T>) tx.streamActivities(types);
return this;
}
//
// Expressions
//
protected final StrolchSearch where(SearchExpression searchExpression) {
protected StrolchSearch where(SearchExpression searchExpression) {
if (this.expression == null)
this.expression = searchExpression;
else
@ -88,82 +83,6 @@ public abstract class StrolchSearch implements Restrictable {
return this;
}
protected final SearchExpression id(SearchPredicate predicate) {
return SearchExpressions.id(predicate);
}
protected final SearchExpression name(SearchPredicate predicate) {
return SearchExpressions.name(predicate);
}
protected final SearchExpression date(SearchPredicate predicate) {
return SearchExpressions.date(predicate);
}
protected final SearchExpression state(SearchPredicate predicate) {
return SearchExpressions.state(predicate);
}
protected final SearchExpression param(String bagId, String paramId, SearchPredicate predicate) {
return SearchExpressions.param(bagId, paramId, predicate);
}
protected final SearchExpression paramNull(String bag, String param) {
return SearchExpressions.paramNull(bag, param);
}
protected final SearchExpression not(SearchExpression expression) {
return element -> !expression.matches(element);
}
//
// Predicates
//
protected final SearchPredicate isEqualTo(Object value) {
return SearchPredicates.isEqualTo(value, false);
}
protected final SearchPredicate isEqualToIgnoreCase(Object value) {
return SearchPredicates.isEqualTo(value, true);
}
protected final SearchPredicate isNotEqualTo(Object value) {
return SearchPredicates.isEqualTo(value, false).not();
}
protected final SearchPredicate isNotEqualToIgnoreCase(Object value) {
return SearchPredicates.isEqualTo(value, true).not();
}
protected final SearchPredicate startsWith(Object value) {
return SearchPredicates.startsWith(value, false);
}
protected final SearchPredicate startsWithIgnoreCase(Object value) {
return SearchPredicates.startsWith(value, true);
}
protected final SearchPredicate endsWith(Object value) {
return SearchPredicates.endsWith(value, false);
}
protected final SearchPredicate endsWithIgnoreCase(Object value) {
return SearchPredicates.endsWith(value, true);
}
protected final SearchPredicate contains(Object value) {
return SearchPredicates.contains(value, false);
}
protected final SearchPredicate containsIgnoreCase(Object value) {
return SearchPredicates.contains(value, true);
}
protected final SearchPredicate inRange(DateRange range) {
return SearchPredicates.inRange(range);
}
/**
* @see li.strolch.privilege.model.Restrictable#getPrivilegeName()
*/

View File

@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
public class StrolchSearchTest {
public static final Logger logger = LoggerFactory.getLogger(ParallelTests.class);
public static final Logger logger = LoggerFactory.getLogger(StrolchSearchTest.class);
private static final String TARGET_PATH = "target/" + StrolchSearchTest.class.getSimpleName();
private static final String SOURCE_PATH = "src/test/resources/transienttest";
@ -108,7 +108,6 @@ public class StrolchSearchTest {
// do search, returns SearchResult
.search(tx)
// transform, either:
.asResources() // asOrders(), asActivities() or map to something entirely different:
.map(a -> a.accept(toJsonVisitor))
// finishing as toList, toSet, toPaging
.toList();
@ -122,15 +121,15 @@ public class StrolchSearchTest {
StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert);
try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class)) {
List<Order> result = new StrolchSearch() {
List<Order> result = new StrolchSearch<Order>() {
@Override
public StrolchSearch prepare() {
public void define() {
DateRange dateRange = new DateRange() //
.from(ISO8601FormatFactory.getInstance().parseDate("2012-01-01T00:00:00.000+01:00"), true)
.to(ISO8601FormatFactory.getInstance().parseDate("2013-01-01T00:00:00.000+01:00"), true);
return orders() //
orders() //
.where(date(isEqualTo(new Date(1384929777699L))) //
.or(state(isEqualTo(State.CREATED)) //
.and(param(BAG_ID, PARAM_STRING_ID, isEqualTo("Strolch"))) //
@ -138,7 +137,6 @@ public class StrolchSearchTest {
}
} //
.search(tx) //
.asOrders() //
.toList();
assertEquals(7, result.size());
@ -150,14 +148,14 @@ public class StrolchSearchTest {
StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert);
try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class)) {
StrolchSearch search = new StrolchSearch() {
StrolchSearch<Order> search = new StrolchSearch<Order>() {
@Override
public StrolchSearch prepare() {
return orders("SortingType").where(state(isEqualTo(State.CREATED)));
public void define() {
orders("SortingType").where(state(isEqualTo(State.CREATED)));
}
};
List<StrolchRootElement> result;
List<Order> result;
result = search //
.search(tx) //
@ -180,24 +178,23 @@ public class StrolchSearchTest {
StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert);
try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class)) {
Map<String, State> states = new StrolchSearch() {
Map<String, State> states = new StrolchSearch<Activity>() {
@Override
public StrolchSearch prepare() {
return activities() //
.where(state(isEqualTo(State.PLANNING)) //
public void define() {
activities() //
.where(state().isEqualTo(State.PLANNING) //
.and(name(isEqualTo("Activity"))) //
);
}
} //
.search(tx) //
.asActivities() //
.toMap(Activity::getId, Activity::getState);
assertEquals(1, states.size());
}
}
public class BallSearch extends StrolchSearch {
public class BallSearch extends StrolchSearch<Resource> {
private String id;
private String status;
@ -210,8 +207,8 @@ public class StrolchSearchTest {
}
@Override
public StrolchSearch prepare() {
return resources("Ball") //
public void define() {
resources("Ball") //
.where(id(isEqualTo(this.id)) //
.or(param("parameters", "status", isEqualTo(this.status)) //
.and(not(param("parameters", "color", isEqualTo(this.color)))) //