[New] Implemented SOQL REST API

This commit is contained in:
Robert von Burg 2018-02-27 18:35:15 +01:00
parent 5745f3c1d1
commit 09469a2b45
8 changed files with 148 additions and 159 deletions

View File

@ -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<JsonObject> 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

View File

@ -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<String, StrolchElementQuery<?>> queries;
// Map of entities the input objects are taken from
protected Map<String, List<StrolchRootElement>> inputCollections;
private Map<String, List<? extends StrolchRootElement>> 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<String, List<StrolchRootElement>> inputCollections) {
void setInputCollections(Map<String, List<? extends StrolchRootElement>> 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<List<StrolchRootElement>> cartesianProduct = buildCartesianProduct(inputCollections, keys);
Object[] keys = this.inputCollections.keySet().toArray();
List<List<StrolchRootElement>> 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<List<StrolchRootElement>> buildCartesianProduct(Map<String, List<StrolchRootElement>> inputCollections,
Object[] keys) {
private List<List<StrolchRootElement>> buildCartesianProduct(
Map<String, List<? extends StrolchRootElement>> 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<StrolchRootElement> elements = inputCollections.get(key);
List<? extends StrolchRootElement> 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<String, List<StrolchRootElement>> queryInputCollection(Map<String, String> entities, StrolchTransaction tx) {
private Map<String, List<? extends StrolchRootElement>> queryInputCollection(Map<String, String> entities,
StrolchTransaction tx) {
Map<String, List<StrolchRootElement>> result = new HashMap<>();
Map<String, List<? extends StrolchRootElement>> result = new HashMap<>();
Set<String> keys = entities.keySet();
for (String key : keys) {
@ -194,44 +187,23 @@ public class QueryProcessor {
switch (clazzKey) {
case "Resource":
List<StrolchRootElement> resources = new ArrayList<>();
ResourceMap resourceMap = tx.getResourceMap();
Set<String> resourceTypes = resourceMap.getTypes(tx);
for (String type : resourceTypes) {
ResourceQuery<Resource> 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<StrolchRootElement> orders = new ArrayList<>();
OrderMap orderMap = tx.getOrderMap();
Set<String> orderTypes = orderMap.getTypes(tx);
for (String type : orderTypes) {
OrderQuery<Resource> 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<StrolchRootElement> activities = new ArrayList<>();
ActivityMap activityMap = tx.getActivityMap();
Set<String> activityTypes = activityMap.getTypes(tx);
for (String type : activityTypes) {
ActivityQuery<Resource> 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;
}
}

View File

@ -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<String, Object> parameter = new HashMap<>();
private Map<String, Object> 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<String> keys = parameter.keySet();
for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {
String key = iterator.next();
Object param = parameter.get(key);
Set<String> 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<Map.Entry<String, JsonElement>> entrySet = params.entrySet();
for (Map.Entry<String, JsonElement> entry : entrySet) {
parameter.put(entry.getKey(), entry.getValue().getAsString());
if (jsonObject.has(PARAMETER)) {
JsonObject params = jsonObject.getAsJsonObject(PARAMETER);
Set<String> keys = params.keySet();
for (String key : keys) {
String value = params.get(key).getAsString();
queryRequest.addParameter(key, value);
}
}
return this;
return queryRequest;
}
}

View File

@ -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;
}
}

View File

@ -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<List<StrolchRootElement>> rows = new ArrayList<>();
private final List<List<StrolchRootElement>> 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<Object> 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<List<StrolchRootElement>> rowsIter = rows.iterator(); rowsIter.hasNext();)
rowsAsJson.add(row2Json(rowsIter.next()));
StrolchElementToJsonVisitor visitor = new StrolchElementToJsonVisitor();
if (flat)
visitor.flat();
for (List<StrolchRootElement> 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<StrolchRootElement> evalResult) {
private JsonArray row2Json(final List<StrolchRootElement> evalResult, StrolchElementToJsonVisitor visitor) {
JsonArray rowAsJson = new JsonArray();
for (Iterator<StrolchRootElement> iterator = evalResult.iterator(); iterator.hasNext();) {
rowAsJson.add(iterator.next().accept(visitor));
for (StrolchRootElement anEvalResult : evalResult) {
rowAsJson.add(anEvalResult.accept(visitor));
}
return rowAsJson;
}

View File

@ -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);
}
}

View File

@ -23,7 +23,7 @@ public class ComparisonExpression extends AbstractBooleanExpression {
@Override
public boolean evaluate(Map<String, Object> inputObjects, Map<String, Object> queryParameter) {
boolean result = false;
boolean result;
switch (operator) {
case "=":

View File

@ -20,7 +20,7 @@ public class QueryProcessorTest extends BaseTest {
List<StrolchRootElement> resources = getTestRessources(10);
List<StrolchRootElement> orders = getTestOrders(10);
Map<String, List<StrolchRootElement>> inputCollections = new HashMap<>();
Map<String, List<? extends StrolchRootElement>> 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<StrolchRootElement> resources = getTestRessources(10);
List<StrolchRootElement> orders = getTestOrders(10);
Map<String, List<StrolchRootElement>> inputCollections = new HashMap<>();
Map<String, List<? extends StrolchRootElement>> inputCollections = new HashMap<>();
inputCollections.put("r", resources);
inputCollections.put("o", orders);