From 30ad0fcaa824faf02554859be77d3c66005ac1d7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 20 Mar 2017 19:27:59 +0100 Subject: [PATCH] [New] Added new generic report creator Create a Report as follows: This creates a report for objects of type Slot, where a Join is done on multiple objects: Slot -> Product -> Section -> Storage -> Location Thus the columns can then be fetched from different joing objects. The joing is done by searching for a Parameter on the joined object on the ParameterBag "relations". See the example XML on how this is done. Or ask eitch@eitchnet.ch to write a proper documentation =)) Filtering is missing, and will be added later --- .../java/li/strolch/report/GenericReport.java | 204 ++++++++++++++++++ .../li/strolch/report/GenericReportTest.java | 81 +++++++ .../reporttest/config/PrivilegeConfig.xml | 35 +++ .../reporttest/config/PrivilegeRoles.xml | 24 +++ .../reporttest/config/PrivilegeUsers.xml | 21 ++ .../config/StrolchConfiguration.xml | 58 +++++ .../reporttest/config/StrolchPolicies.xml | 3 + .../reporttest/data/StrolchModel.xml | 111 ++++++++++ .../test/resources/reporttest/temp/.gitignore | 1 + 9 files changed, 538 insertions(+) create mode 100644 li.strolch.service/src/main/java/li/strolch/report/GenericReport.java create mode 100644 li.strolch.service/src/test/java/li/strolch/report/GenericReportTest.java create mode 100644 li.strolch.service/src/test/resources/reporttest/config/PrivilegeConfig.xml create mode 100644 li.strolch.service/src/test/resources/reporttest/config/PrivilegeRoles.xml create mode 100644 li.strolch.service/src/test/resources/reporttest/config/PrivilegeUsers.xml create mode 100644 li.strolch.service/src/test/resources/reporttest/config/StrolchConfiguration.xml create mode 100644 li.strolch.service/src/test/resources/reporttest/config/StrolchPolicies.xml create mode 100644 li.strolch.service/src/test/resources/reporttest/data/StrolchModel.xml create mode 100644 li.strolch.service/src/test/resources/reporttest/temp/.gitignore diff --git a/li.strolch.service/src/main/java/li/strolch/report/GenericReport.java b/li.strolch.service/src/main/java/li/strolch/report/GenericReport.java new file mode 100644 index 000000000..8e2b1ca1b --- /dev/null +++ b/li.strolch.service/src/main/java/li/strolch/report/GenericReport.java @@ -0,0 +1,204 @@ +package li.strolch.report; + +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import com.google.gson.JsonObject; + +import li.strolch.model.Locator; +import li.strolch.model.ParameterBag; +import li.strolch.model.Resource; +import li.strolch.model.StrolchRootElement; +import li.strolch.model.parameter.Parameter; +import li.strolch.model.parameter.StringParameter; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.runtime.StrolchConstants; +import static li.strolch.utils.helper.StringHelper.*; + +/** + * @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"; + // input + private StrolchTransaction tx; + private String reportId; + + // intermediate + private Resource report; + private ParameterBag columnsBag; + + public GenericReport(StrolchTransaction tx, String reportId) { + this.tx = tx; + this.reportId = reportId; + } + + private Stream> doReport() { + + // get the report + this.report = this.tx.getResourceBy(TYPE_REPORT, this.reportId); + + this.columnsBag = this.report.getParameterBag(BAG_COLUMNS); + + // query the main objects and return a stream + return queryRows().map(e -> evaluateRow(e)); + } + + public Stream doReportAsJson() { + + // generate the stream and map to JsonObject + return doReport().map(row -> { + + // new json object + JsonObject jsonObject = new JsonObject(); + + // add columns + evaluateColumns(row).forEach(e -> jsonObject.addProperty(e.getKey(), e.getValue())); + + return jsonObject; + }); + } + + private Stream> evaluateColumns(Map row) { + + // get iterator to columns + Iterable> iterable = () -> this.columnsBag.getParameters().iterator(); + + // generate a stream + return StreamSupport.stream(iterable.spliterator(), false).map(p -> { + + // create the column id/value pair + + StringParameter columnDefP = (StringParameter) p; + + String columnId = columnDefP.getId(); + String refType = columnDefP.getUom(); + String columnDef = columnDefP.getValue(); + + // get the referenced object + StrolchRootElement column = row.get(refType); + + String columnValue; + if (column == null) { + columnValue = DASH; + } else if (columnDef.equals(COL_NAME)) { + columnValue = column.getName(); + } 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); + columnValue = param.getValueAsString(); + } + + return new SimpleImmutableEntry<>(columnId, columnValue); + }); + } + + private Stream queryRows() { + + // find the type of object for which the report is created + StringParameter objectTypeP = this.report.getParameter(BAG_PARAMETERS, PARAM_OBJECT_TYPE); + + if (objectTypeP.getInterpretation().equals(StrolchConstants.INTERPRETATION_RESOURCE_REF)) { + + return this.tx.getResourceMap().getElementsBy(this.tx, objectTypeP.getUom()).stream() + .map(e -> e.getRootElement()); + + } else if (objectTypeP.getInterpretation().equals(StrolchConstants.INTERPRETATION_ORDER_REF)) { + + return this.tx.getOrderMap().getElementsBy(this.tx, objectTypeP.getUom()).stream() + .map(e -> e.getRootElement()); + + } else { + + throw new IllegalArgumentException("Unhandled element type " + objectTypeP.getInterpretation()); + } + } + + private HashMap evaluateRow(StrolchRootElement resource) { + + // interpretation -> Resource-Ref, etc. + // uom -> object type + // value -> element type where relation is defined for this join + ParameterBag joinBag = this.report.getParameterBag(BAG_JOINS); + + // create the refs element + HashMap refs = new HashMap<>(); + // and add the starting point + refs.put(resource.getType(), resource); + + for (String paramId : joinBag.getParameterKeySet()) { + StringParameter joinP = joinBag.getParameter(paramId); + addColumnJoin(refs, joinBag, joinP, true); + } + + return refs; + } + + private StrolchRootElement addColumnJoin(HashMap refs, ParameterBag joinBag, + StringParameter joinP, boolean optional) { + + String elementType = joinP.getInterpretation().substring(0, joinP.getInterpretation().indexOf(SUFFIX_REF)); + String joinType = joinP.getUom(); + String dependencyType = joinP.getValue(); + + // get dependency + StrolchRootElement dependency; + if (refs.containsKey(dependencyType)) { + dependency = refs.get(dependencyType); + } else { + // recursively find the dependency + StringParameter dependencyP = joinBag.getParameter(dependencyType); + dependency = addColumnJoin(refs, joinBag, dependencyP, false); + } + + ParameterBag relationsBag = dependency.getParameterBag(BAG_RELATIONS); + List> relationParams = relationsBag.getParameters().stream() + .filter(p -> p.getUom().equals(joinType)).collect(Collectors.toList()); + if (relationParams.isEmpty()) { + throw new IllegalStateException( + "Found no relation parameters with UOM " + joinType + " on dependency " + dependency.getLocator()); + } + if (relationParams.size() > 1) { + throw new IllegalStateException("Found multiple possible relation parameters for UOM " + joinType + + " on dependency " + dependency.getLocator()); + } + + StringParameter relationP = (StringParameter) relationParams.get(0); + if (relationP.getValue().isEmpty() && optional) { + refs.put(joinType, null); + return null; + } + + Locator locator = Locator.valueOf(elementType, joinType, relationP.getValue()); + StrolchRootElement joinElem; + try { + joinElem = this.tx.findElement(locator); + } catch (Exception e) { + throw new IllegalStateException("Failed to find join element " + joinType + " for dependency " + + dependency.getLocator() + " with locator " + locator); + } + + refs.put(joinType, joinElem); + return joinElem; + } +} diff --git a/li.strolch.service/src/test/java/li/strolch/report/GenericReportTest.java b/li.strolch.service/src/test/java/li/strolch/report/GenericReportTest.java new file mode 100644 index 000000000..206acaabc --- /dev/null +++ b/li.strolch.service/src/test/java/li/strolch/report/GenericReportTest.java @@ -0,0 +1,81 @@ +package li.strolch.report; + +import static org.junit.Assert.assertEquals; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; +import li.strolch.testbase.runtime.RuntimeMock; + +public class GenericReportTest { + + private static final String RUNTIME_PATH = "target/GenericReportTest/"; //$NON-NLS-1$ + private static final String CONFIG_SRC = "src/test/resources/reporttest"; //$NON-NLS-1$ + + private static RuntimeMock runtimeMock; + private static Certificate certificate; + + @BeforeClass + public static void beforeClass() { + runtimeMock = new RuntimeMock().mockRuntime(RUNTIME_PATH, CONFIG_SRC); + runtimeMock.startContainer(); + certificate = runtimeMock.loginTest(); + } + + @AfterClass + public static void afterClass() { + runtimeMock.logout(certificate); + runtimeMock.destroyRuntime(); + } + + @Test + public void test() { + + try (StrolchTransaction tx = runtimeMock.openUserTx(certificate)) { + + GenericReport report = new GenericReport(tx, "stockReport"); + report.doReportAsJson().forEach(e -> { + + if (e.get("slot").getAsString().equals("Slot 1")) { + + assertEquals("Product 01", e.get("product").getAsString()); + assertEquals("20.0", e.get("quantity").getAsString()); + assertEquals("40.0", e.get("maxQuantity").getAsString()); + assertEquals("Section 001", e.get("section").getAsString()); + assertEquals("Storage 01", e.get("storage").getAsString()); + assertEquals("Location 01", e.get("location").getAsString()); + + } else if (e.get("slot").getAsString().equals("Slot 2")) { + + assertEquals("Product 02", e.get("product").getAsString()); + assertEquals("18.0", e.get("quantity").getAsString()); + assertEquals("20.0", e.get("maxQuantity").getAsString()); + assertEquals("Section 001", e.get("section").getAsString()); + assertEquals("Storage 01", e.get("storage").getAsString()); + assertEquals("Location 01", e.get("location").getAsString()); + + } else if (e.get("slot").getAsString().equals("Slot 3")) { + + assertEquals("Product 01", e.get("product").getAsString()); + assertEquals("11.0", e.get("quantity").getAsString()); + assertEquals("40.0", e.get("maxQuantity").getAsString()); + assertEquals("Section 002", e.get("section").getAsString()); + assertEquals("Storage 02", e.get("storage").getAsString()); + assertEquals("Location 02", e.get("location").getAsString()); + + } else if (e.get("slot").getAsString().equals("Slot 4")) { + + assertEquals("Product 02", e.get("product").getAsString()); + assertEquals("16.0", e.get("quantity").getAsString()); + assertEquals("20.0", e.get("maxQuantity").getAsString()); + assertEquals("Section 002", e.get("section").getAsString()); + assertEquals("Storage 02", e.get("storage").getAsString()); + assertEquals("Location 02", e.get("location").getAsString()); + } + }); + } + } +} diff --git a/li.strolch.service/src/test/resources/reporttest/config/PrivilegeConfig.xml b/li.strolch.service/src/test/resources/reporttest/config/PrivilegeConfig.xml new file mode 100644 index 000000000..5ccaf8559 --- /dev/null +++ b/li.strolch.service/src/test/resources/reporttest/config/PrivilegeConfig.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/reporttest/config/PrivilegeRoles.xml b/li.strolch.service/src/test/resources/reporttest/config/PrivilegeRoles.xml new file mode 100644 index 000000000..ed80a6a3d --- /dev/null +++ b/li.strolch.service/src/test/resources/reporttest/config/PrivilegeRoles.xml @@ -0,0 +1,24 @@ + + + + + li.strolch.runtime.privilege.StrolchSystemAction + li.strolch.runtime.privilege.StrolchSystemActionWithResult + li.strolch.persistence.postgresql.PostgreSqlSchemaInitializer + + + true + + + true + + + + + true + + + true + + + diff --git a/li.strolch.service/src/test/resources/reporttest/config/PrivilegeUsers.xml b/li.strolch.service/src/test/resources/reporttest/config/PrivilegeUsers.xml new file mode 100644 index 000000000..411d37116 --- /dev/null +++ b/li.strolch.service/src/test/resources/reporttest/config/PrivilegeUsers.xml @@ -0,0 +1,21 @@ + + + + SYSTEM + + agent + + + + Application + Administrator + ENABLED + en_GB + + AppUser + + + + + + diff --git a/li.strolch.service/src/test/resources/reporttest/config/StrolchConfiguration.xml b/li.strolch.service/src/test/resources/reporttest/config/StrolchConfiguration.xml new file mode 100644 index 000000000..fe29fa8bb --- /dev/null +++ b/li.strolch.service/src/test/resources/reporttest/config/StrolchConfiguration.xml @@ -0,0 +1,58 @@ + + + + + StrolchRuntimeTest + + true + + + + PrivilegeHandler + li.strolch.runtime.privilege.PrivilegeHandler + li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler + + PrivilegeConfig.xml + + + + RealmHandler + li.strolch.agent.api.RealmHandler + li.strolch.agent.impl.DefaultRealmHandler + PrivilegeHandler + + execution + SECONDS + 1 + TRANSIENT + StrolchModel.xml + + + + + ServiceHandler + li.strolch.service.api.ServiceHandler + li.strolch.service.api.DefaultServiceHandler + + true + + + + + PolicyHandler + li.strolch.policy.PolicyHandler + li.strolch.policy.DefaultPolicyHandler + + true + StrolchPolicies.xml + + + + + ExecutionHandler + li.strolch.execution.ExecutionHandler + li.strolch.execution.EventBasedExecutionHandler + + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/reporttest/config/StrolchPolicies.xml b/li.strolch.service/src/test/resources/reporttest/config/StrolchPolicies.xml new file mode 100644 index 000000000..cba3b97d9 --- /dev/null +++ b/li.strolch.service/src/test/resources/reporttest/config/StrolchPolicies.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/reporttest/data/StrolchModel.xml b/li.strolch.service/src/test/resources/reporttest/data/StrolchModel.xml new file mode 100644 index 000000000..337ebe6c5 --- /dev/null +++ b/li.strolch.service/src/test/resources/reporttest/data/StrolchModel.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/reporttest/temp/.gitignore b/li.strolch.service/src/test/resources/reporttest/temp/.gitignore new file mode 100644 index 000000000..738567283 --- /dev/null +++ b/li.strolch.service/src/test/resources/reporttest/temp/.gitignore @@ -0,0 +1 @@ +/sessions.dat