package li.strolch.report.policy;
import static java.util.Comparator.comparingInt;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.report.ReportConstants.*;
import static li.strolch.utils.helper.StringHelper.EMPTY;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.gson.JsonObject;
import li.strolch.model.*;
import li.strolch.model.parameter.AbstractParameter;
import li.strolch.model.parameter.DateParameter;
import li.strolch.model.parameter.Parameter;
import li.strolch.model.parameter.StringParameter;
import li.strolch.model.policy.PolicyDef;
import li.strolch.model.visitor.ElementDateVisitor;
import li.strolch.model.visitor.ElementStateVisitor;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.policy.PolicyHandler;
import li.strolch.report.ReportConstants;
import li.strolch.report.ReportElement;
import li.strolch.runtime.StrolchConstants;
import li.strolch.utils.ObjectHelper;
import li.strolch.utils.collections.DateRange;
import li.strolch.utils.collections.MapOfLists;
import li.strolch.utils.collections.MapOfSets;
import li.strolch.utils.collections.TypedTuple;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
/**
* A Generic Report defines a report as is described at Strolch
* Reports
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class GenericReport extends ReportPolicy {
private Resource reportRes;
private ParameterBag columnsBag;
private List orderingParams;
private Map filterCriteriaParams;
private boolean descending;
private boolean allowMissingColumns;
private List columnIds;
private StringParameter dateRangeSelP;
private DateRange dateRange;
private Map> filtersByPolicy;
private MapOfSets filtersById;
private long counter;
protected JsonObject i18nData;
public GenericReport(StrolchTransaction tx) {
super(tx);
}
/**
* Retrieves the {@code Resource} with the given ID, and initializes this instance with the data specified on the
* report
*
* @param reportId
* the report to use
*/
public void initialize(String reportId) {
// get the reportRes
this.reportRes = tx().getResourceBy(TYPE_REPORT, reportId, true);
StringParameter objectTypeP = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_OBJECT_TYPE, true);
String objectType = objectTypeP.getValue();
this.columnsBag = this.reportRes.getParameterBag(BAG_COLUMNS, true);
this.columnIds = this.columnsBag.getParameters().stream() //
.sorted(comparingInt(Parameter::getIndex)) //
.map(StrolchElement::getId) //
.collect(Collectors.toList());
if (this.reportRes.hasParameter(BAG_PARAMETERS, PARAM_DESCENDING))
this.descending = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_DESCENDING).getValue();
if (this.reportRes.hasParameter(BAG_PARAMETERS, PARAM_ALLOW_MISSING_COLUMNS))
this.allowMissingColumns = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_ALLOW_MISSING_COLUMNS)
.getValue();
this.dateRangeSelP = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_DATE_RANGE_SEL);
// evaluate filter criteria params
this.filterCriteriaParams = new HashMap<>();
StringParameter objectTypeFilterCriteriaP = objectTypeP.getClone();
objectTypeFilterCriteriaP.setId(objectType);
this.filterCriteriaParams.put(objectType, objectTypeFilterCriteriaP);
if (this.reportRes.hasParameterBag(BAG_JOINS)) {
ParameterBag joinBag = this.reportRes.getParameterBag(BAG_JOINS);
joinBag.getParameters()
.forEach(parameter -> filterCriteriaParams.put(parameter.getId(), (StringParameter) parameter));
}
if (this.reportRes.hasParameterBag(BAG_ADDITIONAL_TYPE)) {
ParameterBag additionalTypeBag = this.reportRes.getParameterBag(BAG_ADDITIONAL_TYPE);
StringParameter additionalTypeP = additionalTypeBag.getParameter(PARAM_OBJECT_TYPE, true);
this.filterCriteriaParams.put(additionalTypeP.getValue(), additionalTypeP);
}
if (this.reportRes.hasParameterBag(BAG_ADDITIONAL_JOINS)) {
ParameterBag joinBag = this.reportRes.getParameterBag(BAG_ADDITIONAL_JOINS);
joinBag.getParameters()
.forEach(parameter -> filterCriteriaParams.put(parameter.getId(), (StringParameter) parameter));
}
// evaluate ordering params
if (this.reportRes.hasParameterBag(BAG_ORDERING)) {
ParameterBag orderingBag = this.reportRes.getParameterBag(BAG_ORDERING, true);
if (orderingBag.hasParameters()) {
this.orderingParams = orderingBag.getParameters().stream().map(e -> (StringParameter) e)
.collect(Collectors.toList());
this.orderingParams.sort(comparingInt(AbstractParameter::getIndex));
}
}
// evaluate filters
this.filtersByPolicy = new HashMap<>();
List filterBags = this.reportRes.getParameterBagsByType(TYPE_FILTER);
for (ParameterBag filterBag : filterBags) {
if (filterBag.hasParameter(PARAM_FIELD_REF) && (filterBag.hasParameter(PARAM_FIELD_REF1) || filterBag
.hasParameter(PARAM_FIELD_REF2))) {
throw new IllegalArgumentException(
"Filter " + filterBag.getLocator() + " can not have combination of " + PARAM_FIELD_REF
+ " and any of " + PARAM_FIELD_REF1 + ", " + PARAM_FIELD_REF2);
} else if ((filterBag.hasParameter(PARAM_FIELD_REF1) && !filterBag.hasParameter(PARAM_FIELD_REF2)) || (
!filterBag.hasParameter(PARAM_FIELD_REF1) && filterBag.hasParameter(PARAM_FIELD_REF2))) {
throw new IllegalArgumentException(
"Filter " + filterBag.getLocator() + " must have both " + PARAM_FIELD_REF1 + " and "
+ PARAM_FIELD_REF2);
} else if (!filterBag.hasParameter(PARAM_FIELD_REF) && (!filterBag.hasParameter(PARAM_FIELD_REF1)
|| !filterBag.hasParameter(PARAM_FIELD_REF2))) {
throw new IllegalArgumentException(
"Filter " + filterBag.getLocator() + " is missing the " + PARAM_FIELD_REF + " or "
+ PARAM_FIELD_REF1 + ", " + PARAM_FIELD_REF2 + " combination!");
}
// prepare filter function policy
StringParameter functionP = filterBag.getParameter(PARAM_POLICY);
PolicyHandler policyHandler = getContainer().getComponent(PolicyHandler.class);
PolicyDef policyDef = PolicyDef.valueOf(functionP.getInterpretation(), functionP.getUom());
ReportFilterPolicy filterFunction = policyHandler.getPolicy(policyDef, tx());
filterFunction.init(functionP.getValue());
TypedTuple refTuple = new TypedTuple<>();
if (filterBag.hasParameter(PARAM_FIELD_REF)) {
refTuple.setFirst(filterBag.getParameter(PARAM_FIELD_REF));
} else {
refTuple.setFirst(filterBag.getParameter(PARAM_FIELD_REF1));
refTuple.setSecond(filterBag.getParameter(PARAM_FIELD_REF2));
}
this.filtersByPolicy.put(filterFunction, refTuple);
}
}
public boolean isDescending() {
return this.descending;
}
public void setI18nData(JsonObject i18nData) {
this.i18nData = i18nData;
}
public long getCounter() {
return this.counter;
}
/**
* Returns true if the report has a date range selector specified
*
* @return true if the report has a date range selector specified
*/
public boolean hasDateRangeSelector() {
return this.dateRangeSelP != null;
}
/**
* Sets the given date range
*
* @param dateRange
* the date range to set
*
* @return this for chaining
*/
public GenericReport dateRange(DateRange dateRange) {
this.dateRange = dateRange;
return this;
}
/**
* Returns the currently set {@link DateRange} or null if not set
*
* @return the date range, or null if not set
*/
public DateRange getDateRange() {
return this.dateRange;
}
/**
* The keys for the header of this report, as is defined on the {@link ReportConstants#BAG_COLUMNS} parameter bag
*
* @return the keys for the header
*/
public List getColumnKeys() {
return this.columnIds;
}
/**
* Applies the given filter for the given element type
*
* @param type
* the type of element to filter
* @param ids
* the IDs of the elements to filter to
*
* @return this for chaining
*/
public GenericReport filter(String type, String... ids) {
if (this.filtersById == null)
this.filtersById = new MapOfSets<>();
for (String id : ids) {
this.filtersById.addElement(type, id);
}
return this;
}
/**
* Applies the given filter for the given element type
*
* @param type
* the type of element to filter
* @param ids
* the IDs of the elements to filter to
*
* @return this for chaining
*/
public GenericReport filter(String type, List ids) {
if (this.filtersById == null)
this.filtersById = new MapOfSets<>();
for (String id : ids) {
this.filtersById.addElement(type, id);
}
return this;
}
/**
* Applies the given filter for the given element type
*
* @param type
* the type of element to filter
* @param ids
* the IDs of the elements to filter to
*
* @return this for chaining
*/
public GenericReport filter(String type, Set ids) {
if (this.filtersById == null)
this.filtersById = new MapOfSets<>();
for (String id : ids) {
this.filtersById.addElement(type, id);
}
return this;
}
protected void incrementCounter() {
this.counter++;
}
/**
* Builds the stream of rows on which further transformations can be performed. Each row is a {@link Map} for where
* the key is an element type, and the value is the associated element
*
* @return this for chaining
*/
public Stream