[New] GenericReport: DateRange filter, incl. $id, $type, $date, $state

This commit is contained in:
Robert von Burg 2017-03-21 15:34:10 +01:00
parent 9b2fc22cc0
commit 9c45bf2ec3
3 changed files with 288 additions and 44 deletions

View File

@ -3,6 +3,7 @@ package li.strolch.report;
import static li.strolch.utils.helper.StringHelper.DASH;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -17,42 +18,65 @@ import li.strolch.model.Locator;
import li.strolch.model.ParameterBag;
import li.strolch.model.Resource;
import li.strolch.model.StrolchRootElement;
import li.strolch.model.StrolchValueType;
import li.strolch.model.parameter.DateParameter;
import li.strolch.model.parameter.Parameter;
import li.strolch.model.parameter.StringParameter;
import li.strolch.model.visitor.ElementDateVisitor;
import li.strolch.model.visitor.ElementStateVisitor;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.runtime.StrolchConstants;
import li.strolch.utils.collections.DateRange;
import li.strolch.utils.collections.MapOfSets;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class GenericReport {
private static final String BAG_RELATIONS = "relations";
private static final String SUFFIX_REF = "-Ref";
private static final String BAG_JOINS = "joins";
private static final String PARAM_OBJECT_TYPE = "objectType";
private static final String BAG_PARAMETERS = "parameters";
private static final String COL_NAME = "$name";
private static final String BAG_COLUMNS = "columns";
private static final String TYPE_REPORT = "Report";
private static final String BAG_RELATIONS = "relations";
private static final String BAG_JOINS = "joins";
private static final String BAG_PARAMETERS = "parameters";
private static final String BAG_COLUMNS = "columns";
private static final String PARAM_OBJECT_TYPE = "objectType";
private static final String PARAM_DATE_RANGE_SEL = "dateRangeSel";
private static final String COL_ID = "$id";
private static final String COL_NAME = "$name";
private static final String COL_TYPE = "$type";
private static final String COL_STATE = "$state";
private static final String COL_DATE = "$date";
private static final String SUFFIX_REF = "-Ref";
// input
private StrolchTransaction tx;
private String reportId;
private MapOfSets<String, String> filtersByType;
// intermediate
private Resource report;
private ParameterBag columnsBag;
private StringParameter dateRangeSelP;
private DateRange dateRange;
private MapOfSets<String, String> filtersByType;
public GenericReport(StrolchTransaction tx, String reportId) {
this.tx = tx;
this.reportId = reportId;
this.filtersByType = new MapOfSets<>();
}
public GenericReport dateRange(DateRange dateRange) {
this.dateRange = dateRange;
return this;
}
public GenericReport filter(String type, String... ids) {
if (this.filtersByType == null)
this.filtersByType = new MapOfSets<>();
for (String id : ids) {
this.filtersByType.addElement(type, id);
}
@ -60,6 +84,8 @@ public class GenericReport {
}
public GenericReport filter(String type, List<String> ids) {
if (this.filtersByType == null)
this.filtersByType = new MapOfSets<>();
for (String id : ids) {
this.filtersByType.addElement(type, id);
}
@ -70,40 +96,69 @@ public class GenericReport {
// get the report
this.report = this.tx.getResourceBy(TYPE_REPORT, this.reportId);
this.columnsBag = this.report.getParameterBag(BAG_COLUMNS);
this.dateRangeSelP = this.report.getParameter(BAG_PARAMETERS, PARAM_DATE_RANGE_SEL);
// query the main objects and return a stream
Stream<Map<String, StrolchRootElement>> stream = queryRows().map(e -> evaluateRow(e));
if (!this.filtersByType.isEmpty())
stream = stream.filter(e -> filter(e));
return stream;
return queryRows().map(e -> evaluateRow(e)).filter(e -> filter(e));
}
public MapOfSets<String, String> generateFilterCriteria() {
public MapOfSets<String, StrolchRootElement> generateFilterCriteria() {
return buildStream() //
.flatMap(e -> e.values().stream()) //
.collect( //
Collector.of( //
() -> new MapOfSets<String, String>(), //
(m, e) -> m.addElement(e.getType(), e.getId()), //
() -> new MapOfSets<String, StrolchRootElement>(), //
(m, e) -> m.addElement(e.getType(), e), //
(m1, m2) -> m1, //
m -> m));
}
private boolean filter(Map<String, StrolchRootElement> row) {
for (String type : this.filtersByType.keySet()) {
// first we do a date range selection, if required
if (this.dateRange != null) {
if (this.dateRangeSelP == null)
throw new IllegalStateException(
"DateRange defined, but report does not defined a date range selector!");
String type = this.dateRangeSelP.getUom();
StrolchRootElement element = row.get(type);
if (element == null)
return false;
if (!this.filtersByType.getSet(type).contains(element.getId()))
String dateRangeSel = this.dateRangeSelP.getValue();
Date date;
if (dateRangeSel.equals(COL_DATE)) {
date = element.accept(new ElementDateVisitor());
} else {
Parameter<?> param = findParameter(this.dateRangeSelP, element);
if (StrolchValueType.parse(param.getType()) != StrolchValueType.DATE)
throw new IllegalStateException(
"Date Range selector is invalid, as referenced parameter is not a Date but a "
+ param.getType());
date = ((DateParameter) param).getValue();
}
if (!this.dateRange.contains(date))
return false;
}
// then we do a filter by criteria
if (this.filtersByType != null && !this.filtersByType.isEmpty()) {
for (String type : this.filtersByType.keySet()) {
StrolchRootElement element = row.get(type);
if (element == null)
return false;
if (!this.filtersByType.getSet(type).contains(element.getId()))
return false;
}
}
// otherwise we want to keep this row
return true;
}
@ -144,18 +199,18 @@ public class GenericReport {
String columnValue;
if (column == null) {
columnValue = DASH;
} else if (columnDef.equals(COL_ID)) {
columnValue = column.getId();
} else if (columnDef.equals(COL_NAME)) {
columnValue = column.getName();
} else if (columnDef.equals(COL_TYPE)) {
columnValue = column.getType();
} else if (columnDef.equals(COL_STATE)) {
columnValue = column.accept(new ElementStateVisitor()).name();
} else if (columnDef.equals(COL_DATE)) {
columnValue = ISO8601FormatFactory.getInstance().formatDate(column.accept(new ElementDateVisitor()));
} else {
String[] locatorParts = columnDef.split(Locator.PATH_SEPARATOR);
if (locatorParts.length != 3)
throw new IllegalStateException(
"Column definition is invalid as column must either be $name, or a three part parameter locator ");
String bagKey = locatorParts[1];
String paramKey = locatorParts[2];
Parameter<?> param = column.getParameter(bagKey, paramKey);
Parameter<?> param = findParameter(columnDefP, column);
columnValue = param.getValueAsString();
}
@ -163,6 +218,25 @@ public class GenericReport {
});
}
private Parameter<?> findParameter(StringParameter paramRefP, StrolchRootElement column) {
String paramRef = paramRefP.getValue();
String[] locatorParts = paramRef.split(Locator.PATH_SEPARATOR);
if (locatorParts.length != 3)
throw new IllegalStateException("Parameter reference (" + paramRef
+ ") is invalid as it does not have 3 parts for " + paramRefP.getLocator());
String bagKey = locatorParts[1];
String paramKey = locatorParts[2];
Parameter<?> param = column.getParameter(bagKey, paramKey);
if (param == null)
throw new IllegalStateException("Parameter reference (" + paramRef + ") for " + paramRefP.getLocator()
+ " not found on " + column.getLocator());
return param;
}
private Stream<StrolchRootElement> queryRows() {
// find the type of object for which the report is created
@ -222,6 +296,10 @@ public class GenericReport {
}
ParameterBag relationsBag = dependency.getParameterBag(BAG_RELATIONS);
if (relationsBag == null)
throw new IllegalStateException("Invalid join definition value: " + joinP.getValue() + " on: "
+ joinP.getLocator() + " as " + dependency.getLocator() + " has no ParameterBag " + BAG_RELATIONS);
List<Parameter<?>> relationParams = relationsBag.getParameters().stream()
.filter(p -> p.getUom().equals(joinType)).collect(Collectors.toList());
if (relationParams.isEmpty()) {

View File

@ -3,15 +3,25 @@ package li.strolch.report;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.gson.JsonObject;
import li.strolch.model.StrolchRootElement;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.Certificate;
import li.strolch.testbase.runtime.RuntimeMock;
import li.strolch.utils.collections.DateRange;
import li.strolch.utils.collections.MapOfSets;
public class GenericReportTest {
@ -127,35 +137,124 @@ public class GenericReportTest {
}
}
@Test
public void shouldFilterReportByDateRange1() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate)) {
Date from = new Date(LocalDate.of(2016, 1, 1).toEpochDay() * 86400000);
Date to = new Date(LocalDate.of(2017, 1, 1).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
// expect no slots as all not in date range
GenericReport report = new GenericReport(tx, "stockReport");
List<JsonObject> result = report.filter("Product", "product01").dateRange(dateRange).doReportAsJson()
.collect(Collectors.toList());
assertTrue(result.isEmpty());
// expect 2 slots, as in date range
to = new Date(LocalDate.of(2017, 3, 1).toEpochDay() * 86400000);
dateRange = new DateRange().from(from, true).to(to, false);
report = new GenericReport(tx, "stockReport");
result = report.filter("Product", "product01").dateRange(dateRange).doReportAsJson()
.collect(Collectors.toList());
assertEquals(2, result.size());
}
}
@Test
public void shouldFilterReportByDateRange2() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate)) {
Date from = new Date(LocalDate.of(2016, 1, 1).toEpochDay() * 86400000);
Date to = new Date(LocalDate.of(2017, 1, 1).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
// expect no orders as all not in date range
GenericReport report = new GenericReport(tx, "fromStockReport");
List<JsonObject> result = report.filter("Product", "product01").dateRange(dateRange).doReportAsJson()
.collect(Collectors.toList());
assertTrue(result.isEmpty());
// expect 2 orders, as in date range
to = new Date(LocalDate.of(2017, 3, 1).toEpochDay() * 86400000);
dateRange = new DateRange().from(from, true).to(to, false);
report = new GenericReport(tx, "fromStockReport");
result = report.filter("Product", "product01").dateRange(dateRange).doReportAsJson()
.collect(Collectors.toList());
assertEquals(2, result.size());
// expect 4 orders, as all in date range
to = new Date(LocalDate.of(2017, 3, 2).toEpochDay() * 86400000);
dateRange = new DateRange().from(from, true).to(to, false);
report = new GenericReport(tx, "fromStockReport");
result = report.filter("Product", "product01", "product02").dateRange(dateRange).doReportAsJson()
.collect(Collectors.toList());
assertEquals(4, result.size());
}
}
@Test
public void shouldCreateFilterCriteria() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate)) {
GenericReport report = new GenericReport(tx, "stockReport");
MapOfSets<String, String> filterCriteria = report.generateFilterCriteria();
MapOfSets<String, StrolchRootElement> filterCriteria = report.generateFilterCriteria();
assertThat(filterCriteria.getSet("Product"), containsInAnyOrder("product01", "product02"));
assertThat(filterCriteria.getSet("Location"), containsInAnyOrder("location01", "location02"));
assertThat(filterCriteria.getSet("Storage"), containsInAnyOrder("storage01", "storage02"));
assertThat(filterCriteria.getSet("Section"), containsInAnyOrder("section001", "section002"));
assertThat(filterCriteria.getSet("Slot"), containsInAnyOrder("slot001", "slot002", "slot003", "slot004"));
assertThat(filterCriteria.getSet("Product").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("product01", "product02"));
assertThat(filterCriteria.getSet("Location").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("location01", "location02"));
assertThat(filterCriteria.getSet("Storage").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("storage01", "storage02"));
assertThat(filterCriteria.getSet("Section").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("section001", "section002"));
assertThat(filterCriteria.getSet("Slot").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("slot001", "slot002", "slot003", "slot004"));
}
}
@Test
public void shouldCreateFilterCriteriaFiltered() {
public void shouldCreateFilterCriteriaFiltered1() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate)) {
GenericReport report = new GenericReport(tx, "stockReport");
MapOfSets<String, String> filterCriteria = report.filter("Product", "product01").generateFilterCriteria();
MapOfSets<String, StrolchRootElement> filterCriteria = report //
.filter("Product", "product01") //
.generateFilterCriteria();
assertThat(filterCriteria.getSet("Product"), containsInAnyOrder("product01"));
assertThat(filterCriteria.getSet("Location"), containsInAnyOrder("location01", "location02"));
assertThat(filterCriteria.getSet("Storage"), containsInAnyOrder("storage01", "storage02"));
assertThat(filterCriteria.getSet("Section"), containsInAnyOrder("section001", "section002"));
assertThat(filterCriteria.getSet("Slot"), containsInAnyOrder("slot001", "slot003"));
assertThat(filterCriteria.getSet("Product").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("product01"));
assertThat(filterCriteria.getSet("Location").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("location01", "location02"));
assertThat(filterCriteria.getSet("Storage").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("storage01", "storage02"));
assertThat(filterCriteria.getSet("Section").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("section001", "section002"));
assertThat(filterCriteria.getSet("Slot").stream().map(e -> e.getId()).collect(Collectors.toSet()),
containsInAnyOrder("slot001", "slot003"));
}
}
@Test
public void shouldCreateFilterCriteriaFiltered2() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate)) {
Date from = new Date(LocalDate.of(2017, 3, 1).toEpochDay() * 86400000);
Date to = new Date(LocalDate.of(2017, 3, 5).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
GenericReport report = new GenericReport(tx, "stockReport");
MapOfSets<String, StrolchRootElement> filterCriteria = report //
.dateRange(dateRange) //
.filter("Product", "product01") //
.generateFilterCriteria();
assertTrue(filterCriteria.isEmpty());
}
}
}

View File

@ -5,6 +5,7 @@
<ParameterBag Id="parameters" Name="parameters" Type="Parameters">
<Parameter Id="objectType" Name="Object Type" Type="String" Interpretation="Resource-Ref" Uom="Slot" Value="Slot" />
<Parameter Id="dateRangeSel" Name="Date Range Selector" Type="String" Interpretation="Resource-Ref" Uom="Product" Value="Bags/parameters/expirationDate" />
</ParameterBag>
<ParameterBag Id="columns" Name="Display Columns" Type="Display">
@ -26,11 +27,77 @@
</Resource>
<Resource Id="fromStockReport" Name="FromStock Report" Type="Report">
<ParameterBag Id="parameters" Name="parameters" Type="Parameters">
<Parameter Id="objectType" Name="Object Type" Type="String" Interpretation="Order-Ref" Uom="FromStock" Value="FromStock" />
<Parameter Id="dateRangeSel" Name="Date Range Selector" Type="String" Interpretation="Order-Ref" Uom="FromStock" Value="$date" />
</ParameterBag>
<ParameterBag Id="columns" Name="Display Columns" Type="Display">
<Parameter Id="location" Name="Location" Type="String" Interpretation="Resource-Ref" Uom="Location" Value="$name" />
<Parameter Id="product" Name="Product" Type="String" Interpretation="Resource-Ref" Uom="Product" Value="$name" />
<Parameter Id="quantity" Name="Quantity" Type="String" Interpretation="Order-Ref" Uom="FromStock" Value="Bags/parameters/quantity" />
<Parameter Id="date" Name="Date" Type="String" Interpretation="Order-Ref" Uom="FromStock" Value="$date" />
<Parameter Id="state" Name="State" Type="String" Interpretation="Order-Ref" Uom="FromStock" Value="$state" />
<Parameter Id="id" Name="Id" Type="String" Interpretation="Order-Ref" Uom="FromStock" Value="$id" />
</ParameterBag>
<ParameterBag Id="joins" Name="Joins" Type="Joins">
<Parameter Id="Product" Name="Product" Type="String" Interpretation="Resource-Ref" Uom="Product" Value="FromStock" />
<Parameter Id="Location" Name="Location" Type="String" Interpretation="Resource-Ref" Uom="Location" Value="FromStock" />
</ParameterBag>
</Resource>
<Resource Id="product01" Name="Product 01" Type="Product">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="expirationDate" Name="Expiration Date" Type="Date" Value="2017-02-01T10:00:00.000+01:00" />
</ParameterBag>
</Resource>
<Resource Id="product02" Name="Product 02" Type="Product">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="expirationDate" Name="Expiration Date" Type="Date" Value="2017-03-01T10:00:00.000+01:00" />
</ParameterBag>
</Resource>
<Order Id="fromStock01" Name="From Stock" Type="FromStock" Date="2017-02-01T10:00:00.000+01:00" State="EXECUTED">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="quantity" Name="quantity" Type="Float" Value="4.0" />
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Parameters">
<Parameter Id="location" Name="Location" Type="String" Interpretation="Resource-Ref" Uom="Location" Value="location01" />
<Parameter Id="product" Name="Product" Type="String" Value="product01" Interpretation="Resource-Ref" Uom="Product" />
</ParameterBag>
</Order>
<Order Id="fromStock02" Name="From Stock" Type="FromStock" Date="2017-02-01T10:00:00.000+01:00" State="EXECUTED">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="quantity" Name="quantity" Type="Float" Value="6.0" />
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Parameters">
<Parameter Id="location" Name="Location" Type="String" Interpretation="Resource-Ref" Uom="Location" Value="location01" />
<Parameter Id="product" Name="Product" Type="String" Value="product01" Interpretation="Resource-Ref" Uom="Product" />
</ParameterBag>
</Order>
<Order Id="fromStock03" Name="From Stock" Type="FromStock" Date="2017-03-01T10:00:00.000+01:00" State="CREATED">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="quantity" Name="quantity" Type="Float" Value="8.0" />
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Parameters">
<Parameter Id="location" Name="Location" Type="String" Interpretation="Resource-Ref" Uom="Location" Value="location02" />
<Parameter Id="product" Name="Product" Type="String" Value="product02" Interpretation="Resource-Ref" Uom="Product" />
</ParameterBag>
</Order>
<Order Id="fromStock04" Name="From Stock" Type="FromStock" Date="2017-03-01T10:00:00.000+01:00" State="CREATED">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="quantity" Name="quantity" Type="Float" Value="12.0" />
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Parameters">
<Parameter Id="location" Name="Location" Type="String" Interpretation="Resource-Ref" Uom="Location" Value="location02" />
<Parameter Id="product" Name="Product" Type="String" Value="product02" Interpretation="Resource-Ref" Uom="Product" />
</ParameterBag>
</Order>
<Resource Id="location01" Name="Location 01" Type="Location">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String" Value="Just a location" />