[Minor] Added JavaDoc to search API

This commit is contained in:
Robert von Burg 2018-06-22 11:11:15 +02:00
parent 39fee69bd0
commit acf3b3ff6c
25 changed files with 370 additions and 8 deletions

View File

@ -2,6 +2,9 @@ package li.strolch.search;
import li.strolch.model.activity.Activity;
/**
* Performs a search for {@link Activity} elements
*/
public class ActivitySearch extends StrolchSearch<Activity> {
private SearchNavigator<Activity> navigator;

View File

@ -3,6 +3,11 @@ package li.strolch.search;
import li.strolch.model.StrolchRootElement;
import li.strolch.utils.collections.DateRange;
/**
* An interface to add search expressions to easily discover the possible search expressions
*
* @param <T>
*/
public interface ExpressionBuilder<T extends StrolchRootElement> {
Object extract(StrolchRootElement element);

View File

@ -5,6 +5,9 @@ import li.strolch.model.activity.Activity;
import li.strolch.model.parameter.Parameter;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
/**
* Implements search expressions to be statically imported when writing searches
*/
public class ExpressionsSupport {
public static <T extends StrolchRootElement> SearchExpression<T> not(SearchExpression<T> expression) {

View File

@ -2,6 +2,9 @@ package li.strolch.search;
import li.strolch.model.Order;
/**
* Performs a search of {@link Order} elements
*/
public class OrderSearch extends StrolchSearch<Order> {
private SearchNavigator<Order> navigator;

View File

@ -3,6 +3,9 @@ package li.strolch.search;
import li.strolch.search.predicates.*;
import li.strolch.utils.collections.DateRange;
/**
* Implements predicates to be used as static imports when writing searches
*/
public class PredicatesSupport {
public static SearchPredicate isEqualTo(Object right) {

View File

@ -2,6 +2,9 @@ package li.strolch.search;
import li.strolch.model.Resource;
/**
* Performs a search for {@link Resource} elements
*/
public class ResourceSearch extends StrolchSearch<Resource> {
private SearchNavigator<Resource> navigator;

View File

@ -7,6 +7,10 @@ import li.strolch.model.Resource;
import li.strolch.model.StrolchRootElement;
import li.strolch.model.activity.Activity;
/**
* Performs a search for any kind of root element, allowing to mix {@link Resource}, {@link Order} and {@link Activity}
* in the result
*/
public class RootElementSearch extends StrolchSearch<StrolchRootElement> {
private SearchNavigator<StrolchRootElement> navigator;

View File

@ -8,12 +8,25 @@ import li.strolch.model.StrolchRootElement;
import li.strolch.model.parameter.Parameter;
import li.strolch.model.visitor.StrolchRootElementVisitor;
/**
* A search result for {@link StrolchSearch} for {@link StrolchRootElement} adding methods specific to root element
*
* @param <T>
*/
public class RootElementSearchResult<T extends StrolchRootElement> extends SearchResult<T> {
public RootElementSearchResult(Stream<T> stream) {
super(stream);
}
/**
* Appends a comparator to the stream of elements to compare by ID
*
* @param reversed
* flag to reverse the comparison
*
* @return this for chaining
*/
public RootElementSearchResult<T> orderById(boolean reversed) {
Comparator<T> comparator = Comparator.comparing(StrolchElement::getId);
if (reversed)
@ -22,6 +35,14 @@ public class RootElementSearchResult<T extends StrolchRootElement> extends Searc
return this;
}
/**
* Appends a comparator to the stream of elements to compare by name
*
* @param reversed
* flag to reverse the comparison
*
* @return this for chaining
*/
public RootElementSearchResult<T> orderByName(boolean reversed) {
Comparator<T> comparator = Comparator.comparing(StrolchElement::getName);
if (reversed)
@ -30,6 +51,18 @@ public class RootElementSearchResult<T extends StrolchRootElement> extends Searc
return this;
}
/**
* Appends a comparator to the stream of elements to compare by a parameter
*
* @param bagId
* the ID of the bag where the parameter is to be found
* @param paramId
* the ID of the parameter to use for comparing
* @param reversed
* flag to reverse the comparison
*
* @return this for chaining
*/
public RootElementSearchResult<T> orderByParam(String bagId, String paramId, boolean reversed) {
Comparator<T> comparator = (o1, o2) -> {
Parameter<?> param1 = o1.getParameter(bagId, paramId);
@ -48,6 +81,36 @@ public class RootElementSearchResult<T extends StrolchRootElement> extends Searc
return this;
}
/**
* <p>Appends a map to the stream which clones the elements in the stream with their version by calling {@link
* StrolchRootElement#getClone(boolean)}</p>
*
* <p>Use this method if you know you are going to modify the elements in the search result.</p>
*
* @return this instance for chaining.
*/
public RootElementSearchResult<T> cloneIfReadOnly() {
this.stream = this.stream.map(e -> {
if (!e.isReadOnly())
return e;
@SuppressWarnings("unchecked")
T clone = (T) e.getClone(true);
return clone;
});
return this;
}
/**
* Transforms this search result to be a search result returning elements with the given visitor
*
* @param visitor
* the visitor to transform this search result's elements
* @param <U>
* the type of element to transform to
*
* @return the new search result for chaining
*/
public <U> SearchResult<U> visitor(StrolchRootElementVisitor<U> visitor) {
return new SearchResult<U>(this.stream.map(e -> e.accept(visitor)));
}

View File

@ -10,18 +10,64 @@ import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A helper class to build search expressions on {@link StrolchRootElement}</p>
*
* <p>This is often used in a web context where searches are performed on multiple parameters etc. of an element</p>
*
* <p>Note that the query string is parsed using the following rules:</p>
* <ul>
* <li>query is trimmed</li>
* <li>empty query means search for everything, i.e. no {@link SearchExpression SearchExpressions} are added</li>
* <li>query is split by space, and each part is handled further:</li>
* <li>format <code>param:&lt;bagId&gt;:&lt;paramId&gt;:&lt;value&gt;</code> adds search expression for given
* bag/param</li>
* <li>otherwise search expression for id and name are added</li>
* <li>all added search expressions are ORed</li>
* </ul>
*/
public class SearchBuilder {
private static final Logger logger = LoggerFactory.getLogger(SearchBuilder.class);
/**
* Builds an {@link OrderSearch} for the given types with the given query
*
* @param query
* the query
* @param types
* the type of orders to search
*
* @return the {@link OrderSearch}
*/
public static OrderSearch buildOrderSearch(String query, String... types) {
return buildSearch(new OrderSearch().types(types), query);
}
/**
* Builds an {@link ResourceSearch} for the given types with the given query
*
* @param query
* the query
* @param types
* the type of resources to search
*
* @return the {@link OrderSearch}
*/
public static ResourceSearch buildResourceSearch(String query, String... types) {
return buildSearch(new ResourceSearch().types(types), query);
}
/**
* Builds an {@link ActivitySearch} for the given types with the given query
*
* @param query
* the query
* @param types
* the type of activities to search
*
* @return the {@link OrderSearch}
*/
public static ActivitySearch buildActivitySearch(String query, String... types) {
return buildSearch(new ActivitySearch().types(types), query);
}

View File

@ -5,35 +5,84 @@ import li.strolch.model.Resource;
import li.strolch.model.StrolchRootElement;
import li.strolch.model.activity.Activity;
/**
* Defines a search expression interface to perform where clauses on {@link StrolchRootElement}
*
* @param <T>
*/
public interface SearchExpression<T extends StrolchRootElement> {
/**
* See if this search expression matches the given element
*
* @param element
* the element to match
*
* @return true if the element is matched with this search expression
*/
boolean matches(T element);
/**
* Returns a new search expression where this search expression is ORed with the given search expression
*
* @param right
* the right hand side of the search expression
*
* @return the new search expression with an internal OR of the two search expressions
*/
default SearchExpression<T> or(SearchExpression<T> right) {
return element -> this.matches(element) || right.matches(element);
}
/**
* Returns a new search expression where this search expression is ANDed with the given search expression
*
* @param right
* the right hand side of the search expression
*
* @return the new search expression with an internal AND of the two search expressions
*/
default SearchExpression<T> and(SearchExpression<T> right) {
return element -> this.matches(element) && right.matches(element);
}
/**
* Negates this search expression
*
* @return a new search expression where this search expression is negated
*/
default SearchExpression<T> not() {
return element -> !this.matches(element);
}
default SearchExpression<Resource> asResourceExp() {
/**
* Map this search expression to a {@link Resource} search expression
*
* @return a new search expression for Resource elements
*/
default SearchExpression<Resource> asResource() {
@SuppressWarnings("unchecked")
SearchExpression<Resource> exp = element -> this.matches((T) element);
return exp;
}
default SearchExpression<Order> asOrderExp() {
/**
* Map this search expression to a {@link Order} search expression
*
* @return a new search expression for Order elements
*/
default SearchExpression<Order> asOrder() {
@SuppressWarnings("unchecked")
SearchExpression<Order> exp = element -> this.matches((T) element);
return exp;
}
default SearchExpression<Activity> asActivityExp() {
/**
* Map this search expression to a {@link Activity} search expression
*
* @return a new search expression for Activity elements
*/
default SearchExpression<Activity> asActivity() {
@SuppressWarnings("unchecked")
SearchExpression<Activity> exp = element -> this.matches((T) element);
return exp;

View File

@ -2,6 +2,11 @@ package li.strolch.search;
import li.strolch.model.StrolchRootElement;
/**
* Declares specific search expressions, i.e. extracting the relevant data for a where clause
*
* @param <T>
*/
public interface SearchExpressions<T extends StrolchRootElement> {
default SearchExpression<T> not(SearchExpression<T> expression) {

View File

@ -5,7 +5,20 @@ import java.util.stream.Stream;
import li.strolch.model.StrolchRootElement;
import li.strolch.persistence.api.StrolchTransaction;
/**
* Navigate the TX to a {@link Stream} of {@link StrolchRootElement}
*
* @param <T>
*/
public interface SearchNavigator<T extends StrolchRootElement> {
/**
* Navigate the TX to a stream of {@link StrolchRootElement}
*
* @param tx
* the TX to navigate
*
* @return a stream of {@link StrolchRootElement}
*/
Stream<T> navigate(StrolchTransaction tx);
}

View File

@ -2,12 +2,38 @@ package li.strolch.search;
import li.strolch.search.predicates.NotPredicate;
/**
* Define the search predicate, i.e. how the where clause is evaluated, or the operator with the right hand side of the
* where clause
*/
public interface SearchPredicate {
/**
* Returns true if this predicate matches the given left hand side of the where clause
*
* @param left
* the left side to match
*
* @return true if the predicate matches
*/
boolean matches(Object left);
/**
* Coerces the internal right handle side of this predicate using the given coercer. This is required to handle
* situations where values are not compatible, i.e. Date object and date string
*
* @param coercer
* the coercer to be applied to the right hand side
*
* @return the new search predicate with the coerced right hand side
*/
SearchPredicate coerce(ValueCoercer coercer);
/**
* Negates this predicated
*
* @return a new predicate where this predicate is negated
*/
default SearchPredicate not() {
return new NotPredicate(this);
}

View File

@ -2,6 +2,9 @@ package li.strolch.search;
import li.strolch.utils.collections.DateRange;
/**
* Declares specific predicates to be performed on a search expression. I.e. defines how the where clause is evaluated
*/
public interface SearchPredicates {
default SearchPredicate isEqualTo(Object right) {

View File

@ -12,6 +12,12 @@ import java.util.stream.Stream;
import li.strolch.utils.collections.Paging;
/**
* A search result for {@link StrolchSearch}. Internally a stream is stored and this class provides methods for
* manipulating the stream
*
* @param <T>
*/
public class SearchResult<T> {
protected Stream<T> stream;
@ -20,39 +26,119 @@ public class SearchResult<T> {
this.stream = stream;
}
/**
* Returns the internal stream
*
* @return the internal stream
*/
public Stream<T> asStream() {
return this.stream;
}
/**
* Returns a new search result converting the elements with the given mapper
*
* @param mapper
* the function to map the elements
* @param <U>
* the new element type
*
* @return the new search result
*/
public <U> SearchResult<U> map(Function<T, U> mapper) {
return new SearchResult<>(this.stream.map(mapper));
}
/**
* Appends a filter to the internal stream
*
* @param predicate
* the predicate to filter the elements
*
* @return this for chaining
*/
public SearchResult<T> filter(Predicate<T> predicate) {
return new SearchResult<>(this.stream.filter(predicate));
this.stream = this.stream.filter(predicate);
return this;
}
/**
* appends a comparator to this stream
*
* @param comparator
* the comparator to append to the stream
*
* @return this for chaining
*/
public SearchResult<T> orderBy(Comparator<? super T> comparator) {
this.stream = this.stream.sorted(comparator);
return this;
}
/**
* Collects this stream to a {@link List}
*
* @return a list of this stream
*/
public List<T> toList() {
return this.stream.collect(Collectors.toList());
}
/**
* Collects this stream to a {@link Set}
*
* @return a set of this stream
*/
public Set<T> toSet() {
return this.stream.collect(Collectors.toSet());
}
/**
* Collects this stream to a {@link Map}, using the given key mapper. The value is returned as is
*
* @param keyMapper
* function to get the key of the element
*
* @return a map of this stream
*/
public <U> Map<U, T> toMap(Function<T, U> keyMapper) {
return this.stream.collect(Collectors.toMap(keyMapper, t -> t));
}
/**
* Collects this stream to a {@link Map}
*
* @param keyMapper
* function to get the key of the element
* @param valueMapper
* function to get the value of the element
*
* @return a map of this stream
*/
public <U, V> Map<U, V> toMap(Function<T, U> keyMapper, Function<T, V> valueMapper) {
return this.stream.collect(Collectors.toMap(keyMapper, valueMapper));
}
/**
* Returns a {@link Paging} element to use this object in paged results
*
* @param offset
* the element offset
* @param limit
* the limit per page
*
* @return the paging
*/
public Paging<T> toPaging(int offset, int limit) {
return Paging.asPage(this.stream.collect(Collectors.toList()), offset, limit);
}
/**
* Performs a simple for each on every element
*
* @param consumer
* the action to perform on each element
*/
public void forEach(Consumer<T> consumer) {
this.stream.forEach(consumer);
}

View File

@ -12,6 +12,11 @@ import li.strolch.privilege.model.Restrictable;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.ExceptionHelper;
/**
* Class to perform searches on Strolch elements
*
* @param <T>
*/
public abstract class StrolchSearch<T extends StrolchRootElement>
implements SearchExpressions<T>, SearchPredicates, Restrictable {
@ -26,7 +31,7 @@ public abstract class StrolchSearch<T extends StrolchRootElement>
protected abstract SearchNavigator<T> getNavigator();
/**
* Used to configure the navigator
* Used to configure the navigator, i.e. which <code>type</code> of root elements are to be queried
*
* @param types
* the types of elements to search
@ -59,8 +64,8 @@ public abstract class StrolchSearch<T extends StrolchRootElement>
}
/**
* Marks this search as an internal search, thus allowing it to be performed without the authenticated user to need
* the required privilege
* Marks this search as an internal search, thus allowing it to be performed without the authenticated user having
* the specific privilege, provided the {@link StrolchModelConstants#INTERNAL} flag is on the search privilege
*
* @return this object for chaining
*/
@ -69,6 +74,14 @@ public abstract class StrolchSearch<T extends StrolchRootElement>
return this;
}
/**
* Performs the actual search, by first validating the privilege context
*
* @param tx
* the TX on which to perform the search
*
* @return the search result
*/
public RootElementSearchResult<T> search(StrolchTransaction tx) {
try {
PrivilegeContext privilegeContext = tx.getContainer().getPrivilegeHandler().validate(tx.getCertificate());
@ -88,7 +101,7 @@ public abstract class StrolchSearch<T extends StrolchRootElement>
if (this.expression != null)
stream = stream.filter(e -> {
return this.expression.matches(e);
});

View File

@ -1,6 +1,12 @@
package li.strolch.search;
/**
* Coerce a given value to a different value
*/
public interface ValueCoercer {
/**
* Coerce the given value to a different value
*/
Object coerce(Object value);
}

View File

@ -3,6 +3,9 @@ package li.strolch.search.predicates;
import li.strolch.search.SearchPredicate;
import li.strolch.search.ValueCoercer;
/**
* Abstract {@link SearchPredicate} implementing coerce method and storing the right hand side of the where clause
*/
public abstract class AbstractSearchPredicate implements SearchPredicate {
private boolean coerced;

View File

@ -2,6 +2,9 @@ package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
/**
* Implements the contains predicate, delegating to {@link ObjectHelper#contains(Object, Object, boolean)}
*/
public class ContainsPredicate extends AbstractSearchPredicate {
private boolean ignoreCase;

View File

@ -2,6 +2,9 @@ package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
/**
* Implements the endsWith predicate, delegating to {@link ObjectHelper#endsWith(Object, Object, boolean)}
*/
public class EndsWithPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;

View File

@ -4,8 +4,14 @@ import java.util.Date;
import li.strolch.search.SearchPredicate;
import li.strolch.search.ValueCoercer;
import li.strolch.utils.ObjectHelper;
import li.strolch.utils.collections.DateRange;
/**
* <p>Implements the date in range predicate.</p>
*
* <b>Note:</b> Can only be used with {@link Date} elements
*/
public class InRangePredicate implements SearchPredicate {
private final DateRange range;

View File

@ -2,6 +2,9 @@ package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
/**
* Implements the equals predicate, delegating to {@link ObjectHelper#equals(Object, Object, boolean)}
*/
public class IsEqualToPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;

View File

@ -2,6 +2,9 @@ package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
/**
* Implements the isIn predicate, delegating to {@link ObjectHelper#isIn(Object, Object, boolean)}
*/
public class IsInPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;

View File

@ -2,7 +2,11 @@ package li.strolch.search.predicates;
import li.strolch.search.SearchPredicate;
import li.strolch.search.ValueCoercer;
import li.strolch.utils.ObjectHelper;
/**
* Implements the not predicate by negating the return value of the internal {@link SearchPredicate}
*/
public class NotPredicate implements SearchPredicate {
private final SearchPredicate predicate;

View File

@ -2,6 +2,9 @@ package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
/**
* Implements the startsWith predicate, delegating to {@link ObjectHelper#startsWith(Object, Object, boolean)}
*/
public class StartsWithPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;