[Major] Better generics support in Search API, includin ExpressionBuilder
This commit is contained in:
parent
131b4bdf83
commit
9bd0d43f50
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
*/
|
||||
|
|
|
@ -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)))) //
|
||||
|
|
Loading…
Reference in New Issue