sirius.biz.web.PageHelper Maven / Gradle / Ivy
Show all versions of sirius-biz Show documentation
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.biz.web;
import com.google.common.collect.Lists;
import sirius.biz.tenants.TenantAware;
import sirius.biz.tenants.Tenants;
import sirius.db.jdbc.SQLQuery;
import sirius.db.mixing.Column;
import sirius.db.mixing.Constraint;
import sirius.db.mixing.Entity;
import sirius.db.mixing.OMA;
import sirius.db.mixing.SmartQuery;
import sirius.db.mixing.constraints.Like;
import sirius.kernel.commons.Limit;
import sirius.kernel.commons.Strings;
import sirius.kernel.commons.Tuple;
import sirius.kernel.commons.Value;
import sirius.kernel.commons.Watch;
import sirius.kernel.di.std.Part;
import sirius.kernel.health.Exceptions;
import sirius.kernel.nls.NLS;
import sirius.web.controller.Facet;
import sirius.web.controller.Page;
import sirius.web.http.WebContext;
import sirius.web.security.UserContext;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* Helper class to build a query, bind it to values given in a {@link WebContext} and create a resulting {@link Page}
* which can be used to render a resulting table and filter box.
*
* @param the generic type of the entities being queries
*/
public class PageHelper {
private static final int DEFAULT_PAGE_SIZE = 25;
private WebContext ctx;
private SmartQuery baseQuery;
private Column[] searchFields;
private boolean advancedSearch;
private List>>> facets = Lists.newArrayList();
private int pageSize = DEFAULT_PAGE_SIZE;
@Part
private static Tenants tenants;
private PageHelper() {
}
/**
* Creates a new instance with the given base query.
*
* @param baseQuery the initial query to execute
* @param the generic entity type being queried
* @return a new instance operating on the given base query
*/
public static PageHelper withQuery(SmartQuery baseQuery) {
PageHelper result = new PageHelper<>();
result.baseQuery = baseQuery;
return result;
}
/**
* Filters the results to only contain entities which belong to the current tenant.
*
* The entity type must extend {@link TenantAware} for this to work.
*
* @return the helper itself for fluent method calls
*/
public PageHelper forCurrentTenant() {
this.baseQuery.eq(TenantAware.TENANT, tenants.getRequiredTenant());
return this;
}
/**
* Attaches a web context to the helper, to fetch filter and pagination values from.
*
* @param ctx the request to attach
* @return the helper itself for fluent method calls
*/
public PageHelper withContext(WebContext ctx) {
this.ctx = ctx;
return this;
}
/**
* Specifies one or more search fields which will be searched if a query
* if given in the WebContext.
*
* @param searchFields the fields to search in
* @return the helper itself for fluent method calls
*/
public PageHelper withSearchFields(Column... searchFields) {
this.searchFields = searchFields;
return this;
}
/**
* Enables the {@link QueryCompiler} which supports SQL like queries and {@link QueryTag}s.
*
* @return the helper itself for fluent method calls
*/
public PageHelper enableAdvancedSearch() {
this.advancedSearch = true;
return this;
}
/**
* Adds a filter facet which will show distinct values of the given property.
*
* @param facet the facet to add
* @return the helper itself for fluent method calls
*/
public PageHelper addFilterFacet(Facet facet) {
return addFacet(facet, (f, q) -> q.eqIgnoreNull(Column.named(f.getName()), f.getValue()));
}
/**
* Adds a filter facet which a custom filter implementation.
*
* @param facet the facet to add
* @param filter the custom logic which determines how a filter value is applied to the query.
* @return the helper itself for fluent method calls
*/
public PageHelper addFacet(Facet facet, BiConsumer> filter) {
return addFacet(facet, filter, null);
}
/**
* Adds a filter facet with custom filter implementation and a custom item computer.
*
* @param facet the facet to add
* @param filter the custom logic which determines how a filter value is applied to the query.
* @param itemsComputer the custom logic which determines the list of items in the filter
* @return the helper itself for fluent method calls
*/
public PageHelper addFacet(Facet facet,
BiConsumer> filter,
BiConsumer> itemsComputer) {
Objects.requireNonNull(baseQuery);
Objects.requireNonNull(ctx);
facet.withValue(ctx.get(facet.getName()).getString());
filter.accept(facet, baseQuery);
facets.add(Tuple.create(facet, itemsComputer));
return this;
}
/**
* Adds a time series based filter which permits to filter on certain time ranges.
*
* @param name the name of the field to filter on
* @param title the title of the filter shown to the user
* @param ranges the ranges which are supported as filter values
* @return the helper itself for fluent method calls
*/
public PageHelper addTimeFacet(String name, String title, DateRange... ranges) {
Facet facet = new Facet(title, name, null, null);
addFacet(facet, (f, q) -> {
for (DateRange range : ranges) {
if (Strings.areEqual(f.getValue(), range.getKey())) {
range.applyTo(name, q);
}
}
});
for (DateRange range : ranges) {
facet.addItem(range.getKey(), range.toString(), -1);
}
return this;
}
/**
* Adds a query based filter which uses the given query to determine which filter items are shown.
*
* @param name the name of the field to filter on
* @param title the title of the filter shown to the user
* @param queryTransformer used to generate the sub-query which determines which filter values to show
* @return the helper itself for fluent method calls
*/
public PageHelper addQueryFacet(String name, String title, Function, SQLQuery> queryTransformer) {
return addFacet(new Facet(title, name, null, null), (f, q) -> {
if (Strings.isFilled(f.getValue())) {
q.eq(Column.named(f.getName()), f.getValue());
}
}, (f, q) -> {
try {
SQLQuery qry = queryTransformer.apply(q);
qry.iterateAll(r -> {
Iterator> iter = r.getFieldsList().iterator();
if (!iter.hasNext()) {
return;
}
String key = Value.of(iter.next().getSecond()).asString();
String label = key;
if (iter.hasNext()) {
label = Value.of(iter.next().getSecond()).asString();
}
f.addItem(key, label, -1);
}, new Limit(0, 100));
} catch (SQLException e) {
Exceptions.handle(OMA.LOG, e);
}
});
}
/**
* Adds a boolean based filter which permits to filter on boolean values.
*
* @param name the name of the field to filter on
* @param title the title of the filter shown to the user
* @return the helper itself for fluent method calls
*/
public PageHelper addBooleanFacet(String name, String title) {
Objects.requireNonNull(ctx);
Facet facet = new Facet(title, name, ctx.get(name).asString(), null);
facet.addItem("true", NLS.get("NLS.yes"), -1);
facet.addItem("false", NLS.get("NLS.no"), -1);
return addFacet(facet, (f, q) -> {
Value filterValue = Value.of(f.getValue());
if (filterValue.isFilled()) {
q.eq(Column.named(f.getName()), filterValue.asBoolean());
}
});
}
/**
* Specifies the number of items shown on the page that gets rendered using this pageHelper.
*
* @param pageSize the number of items shown per page
* @return the helper itself for fluent method calls
*/
public PageHelper withPageSize(int pageSize) {
this.pageSize = pageSize;
return this;
}
/**
* Wraps the given data into a {@link Page} which can be used to render a table, filterbox and support pagination.
*
* @return the given data wrapped as Page
*/
public Page asPage() {
Objects.requireNonNull(ctx);
Watch w = Watch.start();
Page result = new Page().withStart(1).withPageSize(pageSize);
result.bindToRequest(ctx);
if (advancedSearch) {
QueryCompiler compiler =
new QueryCompiler(baseQuery.getEntityDescriptor(), result.getQuery(), searchFields);
Constraint constraint = compiler.compile();
if (constraint != null) {
baseQuery.where(constraint);
}
} else {
if (searchFields != null && searchFields.length > 0) {
baseQuery.where(Like.allWordsInAnyField(result.getQuery(), searchFields));
}
}
for (Tuple>> f : facets) {
if (f.getSecond() != null) {
f.getSecond().accept(f.getFirst(), baseQuery);
}
result.addFacet(f.getFirst());
}
try {
List items = baseQuery.skip(result.getStart() - 1).limit(pageSize + 1).queryList();
if (items.size() > pageSize) {
result.withHasMore(true);
items.remove(items.size() - 1);
}
result.withDuration(w.duration());
result.withItems(items);
} catch (Exception e) {
UserContext.handle(e);
}
return result;
}
}