From 09469a2b45370cf22311939ae197b8ab97068422 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 27 Feb 2018 18:35:15 +0100 Subject: [PATCH] [New] Implemented SOQL REST API --- .../li/strolch/rest/endpoint/ModelQuery.java | 54 ++++----- .../li/strolch/soql/core/QueryProcessor.java | 104 +++++++----------- .../li/strolch/soql/core/QueryRequest.java | 51 +++++---- .../li/strolch/soql/core/QueryResponse.java | 43 ++++---- .../java/li/strolch/soql/core/ResultSet.java | 29 ++--- .../strolch/soql/core/SOQLParseException.java | 5 +- .../expresssion/ComparisonExpression.java | 2 +- .../strolch/soql/core/QueryProcessorTest.java | 19 ++-- 8 files changed, 148 insertions(+), 159 deletions(-) diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java index 10634839b..a3f3fce7e 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java @@ -9,10 +9,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import com.google.gson.*; import li.strolch.agent.api.ActivityMap; import li.strolch.agent.api.OrderMap; import li.strolch.agent.api.ResourceMap; @@ -31,40 +28,41 @@ import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.helper.RestfulHelper; import li.strolch.rest.model.QueryData; +import li.strolch.soql.core.QueryProcessor; +import li.strolch.soql.core.QueryRequest; +import li.strolch.soql.core.QueryResponse; @Path("strolch/model") public class ModelQuery { @POST @Produces(MediaType.APPLICATION_JSON) - @Path("query") - public Response doQuery(@Context HttpServletRequest request, @QueryParam("realmName") String realmName) { - + @Path("soql") + public Response doQuery(@Context HttpServletRequest request, @QueryParam("realmName") String realmName, + @QueryParam("flat") String flat, String data) { Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); - List result = new ArrayList<>(); + JsonObject jsonObject = new JsonParser().parse(data).getAsJsonObject(); + + QueryResponse queryResponse; try (StrolchTransaction tx = openTx(cert, realmName)) { - - StrolchElementToJsonVisitor visitor = new StrolchElementToJsonVisitor(); - visitor.flat(); - - // TODO MS do query - + QueryRequest queryRequest = QueryRequest.fromJson(jsonObject); + QueryProcessor queryProcessor = new QueryProcessor(); + queryResponse = queryProcessor.process(queryRequest, tx); } - JsonObject json = new JsonObject(); - JsonArray arrJ = new JsonArray(); - result.forEach(arrJ::add); - - return Response.ok(json, MediaType.APPLICATION_JSON).build(); + return Response.ok(queryResponse.asJson(Boolean.parseBoolean(flat)).toString(), MediaType.APPLICATION_JSON).build(); } /** * Query {@link Resource Resources} by parsing the query string in {@link QueryData#getQuery()} using * {@link QueryParser} * - * @param queryData the data from the client - * @param request the {@link HttpServletRequest} on which to get the {@link Certificate} + * @param queryData + * the data from the client + * @param request + * the {@link HttpServletRequest} on which to get the {@link Certificate} + * * @return {@link Response} containing the JSONified {@link Resource Resources} queried */ @GET @@ -115,8 +113,11 @@ public class ModelQuery { /** * Query {@link Order Orders} by parsing the query string in {@link QueryData#getQuery()} using {@link QueryParser} * - * @param queryData the data from the client - * @param request the {@link HttpServletRequest} on which to get the {@link Certificate} + * @param queryData + * the data from the client + * @param request + * the {@link HttpServletRequest} on which to get the {@link Certificate} + * * @return {@link Response} containing the JSONified {@link Order Orders} queried */ @GET @@ -168,8 +169,11 @@ public class ModelQuery { * Query {@link Activity Activities} by parsing the query string in {@link QueryData#getQuery()} using * {@link QueryParser} * - * @param queryData the data from the client - * @param request the {@link HttpServletRequest} on which to get the {@link Certificate} + * @param queryData + * the data from the client + * @param request + * the {@link HttpServletRequest} on which to get the {@link Certificate} + * * @return {@link Response} containing the JSONified {@link Activity Activities} queried */ @GET diff --git a/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryProcessor.java b/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryProcessor.java index e95e041e9..a9020ab85 100644 --- a/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryProcessor.java +++ b/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryProcessor.java @@ -1,31 +1,18 @@ package li.strolch.soql.core; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import li.strolch.model.StrolchRootElement; +import li.strolch.model.query.StrolchElementQuery; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.soql.antlr4.generated.SOQLLexer; +import li.strolch.soql.antlr4.generated.SOQLParser; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; -import li.strolch.agent.api.ActivityMap; -import li.strolch.agent.api.OrderMap; -import li.strolch.agent.api.ResourceMap; -import li.strolch.model.Resource; -import li.strolch.model.StrolchRootElement; -import li.strolch.model.query.ActivityQuery; -import li.strolch.model.query.OrderQuery; -import li.strolch.model.query.ResourceQuery; -import li.strolch.model.query.StrolchElementQuery; -import li.strolch.model.query.StrolchTypeNavigation; -import li.strolch.persistence.api.StrolchTransaction; -import li.strolch.soql.antlr4.generated.SOQLLexer; -import li.strolch.soql.antlr4.generated.SOQLParser; - /** * Main class to process a SOQL query. It performs the queries to retrieve the * input objects as defined in the FROM clause and evaluates the compiled @@ -40,20 +27,23 @@ public class QueryProcessor { protected Map> queries; // Map of entities the input objects are taken from - protected Map> inputCollections; + private Map> inputCollections; /** * Set the input map of collections to take the input objects from. For testing * purposes only. * * @param inputCollections + * the input data */ - void setInputCollections(Map> inputCollections) { + void setInputCollections(Map> inputCollections) { this.inputCollections = inputCollections; } /** * @param request + * the query request + * * @return the query response object covering the result set */ public QueryResponse process(QueryRequest request, StrolchTransaction tx) { @@ -65,18 +55,16 @@ public class QueryProcessor { ParseTree tree = parseStatement(statement); compiledStatement = compile(tree); } catch (Exception e) { - // TODO add error handling - e.printStackTrace(); - throw new SOQLParseException(e.getMessage()); + throw new SOQLParseException("Failed to parse String " + statement, e); } // build the input collections, if not set already - if (inputCollections == null) - inputCollections = queryInputCollection(compiledStatement.entities, tx); + if (this.inputCollections == null) + this.inputCollections = queryInputCollection(compiledStatement.entities, tx); // build cartesian product - Object[] keys = inputCollections.keySet().toArray(); - List> cartesianProduct = buildCartesianProduct(inputCollections, keys); + Object[] keys = this.inputCollections.keySet().toArray(); + List> cartesianProduct = buildCartesianProduct(this.inputCollections, keys); ResultSet resultSet = new ResultSet(); @@ -102,12 +90,15 @@ public class QueryProcessor { /** * @param inputCollections + * the data * @param keys + * the keys + * * @return List of Lists of the elements to be taken as input for the compiled - * statement + * statement */ - private List> buildCartesianProduct(Map> inputCollections, - Object[] keys) { + private List> buildCartesianProduct( + Map> inputCollections, Object[] keys) { int numberOfKeys = keys.length; @@ -130,7 +121,7 @@ public class QueryProcessor { // fasten your seat belts, here we go for (int keyIndex = 0; keyIndex < numberOfKeys; keyIndex++) { Object key = keys[keyIndex]; - List elements = inputCollections.get(key); + List elements = inputCollections.get(key); StrolchRootElement element = elements.get(pointer[keyIndex]); row.add(element); } @@ -140,10 +131,8 @@ public class QueryProcessor { /** * parse the string and return the antlr tree - * - * @throws Exception */ - ParseTree parseStatement(String s) throws Exception { + private ParseTree parseStatement(String s) { // build a buffer of tokens pulled from the lexer CharStream input = CharStreams.fromString(s); @@ -161,10 +150,11 @@ public class QueryProcessor { * compile the antlr tree to executable code * * @param tree + * the tree to compile to a statement + * * @return CompiledSOQLStatement - * @throws Exception */ - CompiledStatement compile(ParseTree tree) throws Exception { + private CompiledStatement compile(ParseTree tree) { ParseTreeWalker walker = new ParseTreeWalker(); SOQLListener listener = new SOQLListener(); @@ -180,13 +170,16 @@ public class QueryProcessor { /** * Query all strolch root elements declared in the FROM clause of the query - * + * * @param entities + * the entity types to query + * * @return the input collection */ - Map> queryInputCollection(Map entities, StrolchTransaction tx) { + private Map> queryInputCollection(Map entities, + StrolchTransaction tx) { - Map> result = new HashMap<>(); + Map> result = new HashMap<>(); Set keys = entities.keySet(); for (String key : keys) { @@ -194,44 +187,23 @@ public class QueryProcessor { switch (clazzKey) { case "Resource": - List resources = new ArrayList<>(); - ResourceMap resourceMap = tx.getResourceMap(); - Set resourceTypes = resourceMap.getTypes(tx); - for (String type : resourceTypes) { - ResourceQuery query = new ResourceQuery<>(new StrolchTypeNavigation(type)); - resources.addAll(tx.doQuery(query)); - } - result.put(clazzKey, resources); + result.put(key, tx.getResourceMap().getAllElements(tx)); break; case "Order": - List orders = new ArrayList<>(); - OrderMap orderMap = tx.getOrderMap(); - Set orderTypes = orderMap.getTypes(tx); - for (String type : orderTypes) { - OrderQuery query = new OrderQuery<>(new StrolchTypeNavigation(type)); - orders.addAll(tx.doQuery(query)); - } - result.put(clazzKey, orders); + result.put(key, tx.getOrderMap().getAllElements(tx)); break; case "Activity": - List activities = new ArrayList<>(); - ActivityMap activityMap = tx.getActivityMap(); - Set activityTypes = activityMap.getTypes(tx); - for (String type : activityTypes) { - ActivityQuery query = new ActivityQuery<>(new StrolchTypeNavigation(type)); - activities.addAll(tx.doQuery(query)); - } - result.put(clazzKey, activities); + result.put(key, tx.getActivityMap().getAllElements(tx)); break; default: - String s = "Unable to resolve " + clazzKey + " to strolch root entities."; + String s = "Unable to resolve " + clazzKey + " " + key + " to strolch root entities."; throw new SOQLParseException(s); } } + return result; } - } diff --git a/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryRequest.java b/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryRequest.java index 454355276..2c6b3f0a1 100644 --- a/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryRequest.java +++ b/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryRequest.java @@ -1,14 +1,13 @@ package li.strolch.soql.core; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import li.strolch.model.Tags; - import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Set; +import com.google.gson.JsonObject; +import li.strolch.model.Tags; +import li.strolch.utils.dbc.DBC; + /** * @author msmock */ @@ -18,10 +17,10 @@ public class QueryRequest { public static final String PARAMETER = "queryParameter"; // the SOQL query string - String statement; + private String statement; // the parameter of the SOQL query - Map parameter = new HashMap<>(); + private Map parameter; public String getStatement() { return statement; @@ -39,6 +38,12 @@ public class QueryRequest { this.parameter = parameter; } + public void addParameter(String key, Object value) { + if (this.parameter == null) + this.parameter = new HashMap<>(); + this.parameter.put(key, value); + } + /** * @return the query as JsonObject */ @@ -46,15 +51,14 @@ public class QueryRequest { JsonObject rootJ = new JsonObject(); rootJ.addProperty(Tags.Json.OBJECT_TYPE, "QueryRequest"); - rootJ.addProperty(STATEMENT, statement); + rootJ.addProperty(STATEMENT, this.statement); JsonObject parameterJ = new JsonObject(); rootJ.add(PARAMETER, parameterJ); - Set keys = parameter.keySet(); - for (Iterator iterator = keys.iterator(); iterator.hasNext();) { - String key = iterator.next(); - Object param = parameter.get(key); + Set keys = this.parameter.keySet(); + for (String key : keys) { + Object param = this.parameter.get(key); parameterJ.addProperty(key, param.toString()); } @@ -64,20 +68,25 @@ public class QueryRequest { /** * build request from Json object * - * @return + * @return the query request object */ - public QueryRequest fromJson(JsonObject jsonObject) { + public static QueryRequest fromJson(JsonObject jsonObject) { + QueryRequest queryRequest = new QueryRequest(); + + DBC.PRE.assertTrue("Expected json property " + STATEMENT, jsonObject.has(STATEMENT)); String statement = jsonObject.get(STATEMENT).getAsString(); - setStatement(statement); + queryRequest.setStatement(statement); - JsonObject params = jsonObject.getAsJsonObject(PARAMETER); - Set> entrySet = params.entrySet(); - for (Map.Entry entry : entrySet) { - parameter.put(entry.getKey(), entry.getValue().getAsString()); + if (jsonObject.has(PARAMETER)) { + JsonObject params = jsonObject.getAsJsonObject(PARAMETER); + Set keys = params.keySet(); + for (String key : keys) { + String value = params.get(key).getAsString(); + queryRequest.addParameter(key, value); + } } - return this; + return queryRequest; } - } diff --git a/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryResponse.java b/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryResponse.java index e1285a5d2..7859a0875 100644 --- a/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryResponse.java +++ b/li.strolch.soql/src/main/java/li/strolch/soql/core/QueryResponse.java @@ -1,7 +1,8 @@ package li.strolch.soql.core; -import com.google.gson.JsonObject; +import static li.strolch.utils.helper.StringHelper.isNotEmpty; +import com.google.gson.JsonObject; import li.strolch.model.Tags; /** @@ -9,42 +10,38 @@ import li.strolch.model.Tags; */ public class QueryResponse extends QueryRequest { - static final String RESULT_SET = "resultSet"; + private static final String RESULT_SET = "resultSet"; - // an exception or error message in case of error - public String message; + private String message; - // the returned objects - public ResultSet resultSet = new ResultSet(); + private ResultSet resultSet = new ResultSet(); + + public String getMessage() { + return this.message; + } + + public void setMessage(String message) { + this.message = message; + } - /** - * @param resultSet the resultSet to set - */ public void setResultSet(ResultSet resultSet) { this.resultSet = resultSet; } - /** - * @return the resultSet - */ public ResultSet getResultSet() { - return resultSet; + return this.resultSet; } - /** - * @return the query as JsonObject - */ - public JsonObject asJson() { + public JsonObject asJson(boolean flat) { - JsonObject rootJ = super.asJson(); - rootJ.addProperty(Tags.Json.OBJECT_TYPE, "QueryResponse"); + JsonObject rootJ = super.asJson(); + rootJ.addProperty(Tags.Json.OBJECT_TYPE, "QueryResponse"); - if (message != null && !message.isEmpty()) { - rootJ.addProperty("Message", message); + if (isNotEmpty(this.message)) { + rootJ.addProperty("Message", this.message); } - rootJ.add(RESULT_SET, resultSet.asJson()); + rootJ.add(RESULT_SET, this.resultSet.asJson(flat)); return rootJ; } - } diff --git a/li.strolch.soql/src/main/java/li/strolch/soql/core/ResultSet.java b/li.strolch.soql/src/main/java/li/strolch/soql/core/ResultSet.java index cdc47935b..d3b528513 100644 --- a/li.strolch.soql/src/main/java/li/strolch/soql/core/ResultSet.java +++ b/li.strolch.soql/src/main/java/li/strolch/soql/core/ResultSet.java @@ -1,12 +1,10 @@ package li.strolch.soql.core; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.google.gson.JsonArray; - import li.strolch.model.StrolchRootElement; import li.strolch.model.json.StrolchElementToJsonVisitor; @@ -20,13 +18,11 @@ import li.strolch.model.json.StrolchElementToJsonVisitor; */ public class ResultSet { - final StrolchElementToJsonVisitor visitor = new StrolchElementToJsonVisitor(); - - public final List> rows = new ArrayList<>(); + private final List> rows = new ArrayList<>(); /** * @param row - * the result of the execution of a single statement + * the result of the execution of a single statement */ public void add(final List row) { @@ -40,18 +36,25 @@ public class ResultSet { } } - rows.add(toBeAdded); + this.rows.add(toBeAdded); } /** + * @param flat + * if JSON should be flat or not + * * @return all rows as JSON Array */ - public JsonArray asJson() { + public JsonArray asJson(boolean flat) { JsonArray rowsAsJson = new JsonArray(); - for (Iterator> rowsIter = rows.iterator(); rowsIter.hasNext();) - rowsAsJson.add(row2Json(rowsIter.next())); + StrolchElementToJsonVisitor visitor = new StrolchElementToJsonVisitor(); + if (flat) + visitor.flat(); + + for (List row : this.rows) + rowsAsJson.add(row2Json(row, visitor)); return rowsAsJson; } @@ -59,10 +62,10 @@ public class ResultSet { /** * @return a single row as JSON Array */ - private JsonArray row2Json(final List evalResult) { + private JsonArray row2Json(final List evalResult, StrolchElementToJsonVisitor visitor) { JsonArray rowAsJson = new JsonArray(); - for (Iterator iterator = evalResult.iterator(); iterator.hasNext();) { - rowAsJson.add(iterator.next().accept(visitor)); + for (StrolchRootElement anEvalResult : evalResult) { + rowAsJson.add(anEvalResult.accept(visitor)); } return rowAsJson; } diff --git a/li.strolch.soql/src/main/java/li/strolch/soql/core/SOQLParseException.java b/li.strolch.soql/src/main/java/li/strolch/soql/core/SOQLParseException.java index a71162fd2..ff8ae5494 100644 --- a/li.strolch.soql/src/main/java/li/strolch/soql/core/SOQLParseException.java +++ b/li.strolch.soql/src/main/java/li/strolch/soql/core/SOQLParseException.java @@ -8,7 +8,10 @@ public class SOQLParseException extends RuntimeException { private static final long serialVersionUID = 1L; public SOQLParseException(String message) { - super(message); + super(message); } + public SOQLParseException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/li.strolch.soql/src/main/java/li/strolch/soql/core/expresssion/ComparisonExpression.java b/li.strolch.soql/src/main/java/li/strolch/soql/core/expresssion/ComparisonExpression.java index 4e176d88c..f50e76edc 100644 --- a/li.strolch.soql/src/main/java/li/strolch/soql/core/expresssion/ComparisonExpression.java +++ b/li.strolch.soql/src/main/java/li/strolch/soql/core/expresssion/ComparisonExpression.java @@ -23,7 +23,7 @@ public class ComparisonExpression extends AbstractBooleanExpression { @Override public boolean evaluate(Map inputObjects, Map queryParameter) { - boolean result = false; + boolean result; switch (operator) { case "=": diff --git a/li.strolch.soql/src/test/java/li/strolch/soql/core/QueryProcessorTest.java b/li.strolch.soql/src/test/java/li/strolch/soql/core/QueryProcessorTest.java index 9a1236e9e..c626f08e4 100644 --- a/li.strolch.soql/src/test/java/li/strolch/soql/core/QueryProcessorTest.java +++ b/li.strolch.soql/src/test/java/li/strolch/soql/core/QueryProcessorTest.java @@ -20,7 +20,7 @@ public class QueryProcessorTest extends BaseTest { List resources = getTestRessources(10); List orders = getTestOrders(10); - Map> inputCollections = new HashMap<>(); + Map> inputCollections = new HashMap<>(); inputCollections.put("r", resources); inputCollections.put("o", orders); @@ -31,13 +31,14 @@ public class QueryProcessorTest extends BaseTest { request.getParameter().put("id", "0"); request.setStatement("SELECT r,o FROM Resource r, Order o WHERE r.getId() = :id AND o.getId() = :id"); - String expected = "{\"objectType\":\"QueryResponse\",\"statement\":\"SELECT r,o FROM Resource r, Order o WHERE r.getId() = :id AND " - + "o.getId() = :id\",\"queryParameter\":{\"id\":\"0\"},\"resultSet\":[[{\"objectType\":\"Resource\",\"id\":\"0\",\"name\":null," - + "\"type\":null,\"parameterBags\":{\"testBag\":{\"id\":\"testBag\",\"name\":null,\"type\":null," - + "\"parameters\":{\"testId\":{\"id\":\"testId\",\"name\":null,\"type\":\"Float\",\"value\":\"100.0\"}}}}}," - + "{\"objectType\":\"Order\",\"id\":\"0\",\"name\":null,\"type\":null,\"date\":\"2017-11-01T00:00:00.000+01:00\"," - + "\"state\":\"Created\",\"parameterBags\":{\"testBag\":{\"id\":\"testBag\",\"name\":null,\"type\":null," - + "\"parameters\":{\"testId\":{\"id\":\"testId\",\"name\":null,\"type\":\"Float\",\"value\":\"100.0\"}}}}}]]}"; + String expected = + "{\"objectType\":\"QueryResponse\",\"statement\":\"SELECT r,o FROM Resource r, Order o WHERE r.getId() = :id AND " + + "o.getId() = :id\",\"queryParameter\":{\"id\":\"0\"},\"resultSet\":[[{\"objectType\":\"Resource\",\"id\":\"0\",\"name\":null," + + "\"type\":null,\"parameterBags\":{\"testBag\":{\"id\":\"testBag\",\"name\":null,\"type\":null," + + "\"parameters\":{\"testId\":{\"id\":\"testId\",\"name\":null,\"type\":\"Float\",\"value\":\"100.0\"}}}}}," + + "{\"objectType\":\"Order\",\"id\":\"0\",\"name\":null,\"type\":null,\"date\":\"2017-11-01T00:00:00.000+01:00\"," + + "\"state\":\"Created\",\"parameterBags\":{\"testBag\":{\"id\":\"testBag\",\"name\":null,\"type\":null," + + "\"parameters\":{\"testId\":{\"id\":\"testId\",\"name\":null,\"type\":\"Float\",\"value\":\"100.0\"}}}}}]]}"; Assert.assertEquals(expected, processor.process(request, null).asJson().toString()); } @@ -52,7 +53,7 @@ public class QueryProcessorTest extends BaseTest { List resources = getTestRessources(10); List orders = getTestOrders(10); - Map> inputCollections = new HashMap<>(); + Map> inputCollections = new HashMap<>(); inputCollections.put("r", resources); inputCollections.put("o", orders);