$filter $top $skip $count logic

implemented eq and ne (case-sensitive) for String values, and eq, ne, gt ,ge , lt, le  on the Timestamp field
This commit is contained in:
michaelpede 2021-04-23 16:25:20 -07:00
parent 091af5df2d
commit 5ae46e1f4b
5 changed files with 274 additions and 15 deletions

View File

@ -18,7 +18,10 @@ import org.apache.olingo.server.api.serializer.SerializerResult;
import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceEntitySet; import org.apache.olingo.server.api.uri.UriResourceEntitySet;
import org.apache.olingo.server.api.uri.queryoption.*;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.reso.service.data.meta.FieldInfo; import org.reso.service.data.meta.FieldInfo;
import org.reso.service.data.meta.FilterExpressionVisitor;
import org.reso.service.data.meta.ResourceInfo; import org.reso.service.data.meta.ResourceInfo;
import org.reso.service.edmprovider.RESOedmProvider; import org.reso.service.edmprovider.RESOedmProvider;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -30,6 +33,7 @@ import java.net.URISyntaxException;
import java.sql.*; import java.sql.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
public class GenericEntityCollectionProcessor implements EntityCollectionProcessor public class GenericEntityCollectionProcessor implements EntityCollectionProcessor
@ -82,9 +86,18 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); // in our example, the first segment is the EntitySet UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); // in our example, the first segment is the EntitySet
EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet(); EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet();
boolean isCount = false;
CountOption countOption = uriInfo.getCountOption();
if (countOption != null) {
isCount = countOption.getValue();
if (isCount){
LOG.info("Count str:"+countOption.getText() );
}
}
// 2nd: fetch the data from backend for this requested EntitySetName // 2nd: fetch the data from backend for this requested EntitySetName
// it has to be delivered as EntitySet object // it has to be delivered as EntitySet object
EntityCollection entitySet = getData(edmEntitySet); EntityCollection entitySet = getData(edmEntitySet, uriInfo, isCount);
// 3rd: create a serializer based on the requested format (json) // 3rd: create a serializer based on the requested format (json)
try try
@ -105,7 +118,19 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).build(); ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).build();
final String id = request.getRawBaseUri() + "/" + edmEntitySet.getName(); final String id = request.getRawBaseUri() + "/" + edmEntitySet.getName();
EntityCollectionSerializerOptions opts = EntityCollectionSerializerOptions.with().id(id).contextURL(contextUrl).build(); EntityCollectionSerializerOptions opts = null;
if (isCount) // If there's a $count=true in the query string, we need to have a different formatting options.
{
opts = EntityCollectionSerializerOptions.with()
.contextURL(contextUrl)
.id(id)
.count(countOption)
.build();
}
else
{
opts = EntityCollectionSerializerOptions.with().id(id).contextURL(contextUrl).build();
}
SerializerResult serializerResult = serializer.entityCollection(serviceMetadata, edmEntityType, entitySet, opts); SerializerResult serializerResult = serializer.entityCollection(serviceMetadata, edmEntityType, entitySet, opts);
InputStream serializedContent = serializerResult.getContent(); InputStream serializedContent = serializerResult.getContent();
@ -116,20 +141,85 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
} }
protected EntityCollection getData(EdmEntitySet edmEntitySet){ protected EntityCollection getData(EdmEntitySet edmEntitySet, UriInfo uriInfo, boolean isCount) throws ODataApplicationException {
ArrayList<FieldInfo> fields = this.resourceInfo.getFieldList(); ArrayList<FieldInfo> fields = this.resourceInfo.getFieldList();
EntityCollection lookupsCollection = new EntityCollection(); EntityCollection entCollection = new EntityCollection();
List<Entity> productList = lookupsCollection.getEntities(); List<Entity> productList = entCollection.getEntities();
Map<String, String> properties = System.getenv(); Map<String, String> properties = System.getenv();
try { try {
FilterOption filter = uriInfo.getFilterOption();
String sqlCriteria = null;
if (filter!=null)
{
sqlCriteria = filter.getExpression().accept(new FilterExpressionVisitor(this.resourceInfo));
}
// Statements allow to issue SQL queries to the database // Statements allow to issue SQL queries to the database
Statement statement = connect.createStatement(); Statement statement = connect.createStatement();
// Result set get the result of the SQL query // Result set get the result of the SQL query
ResultSet resultSet = statement.executeQuery("select * from "+this.resourceInfo.getTableName()); String queryString = null;
// Logic for $count
if (isCount)
{
queryString = "select count(*) AS rowcount from " + this.resourceInfo.getTableName();
}
else
{
queryString = "select * from " + this.resourceInfo.getTableName();
}
if (null!=sqlCriteria && sqlCriteria.length()>0)
{
queryString = queryString + " WHERE " + sqlCriteria;
}
// Logic for $top
TopOption topOption = uriInfo.getTopOption();
if (topOption != null) {
int topNumber = topOption.getValue();
if (topNumber >= 0)
{
// Logic for $skip
SkipOption skipOption = uriInfo.getSkipOption();
if (skipOption != null)
{
int skipNumber = skipOption.getValue();
if (skipNumber >= 0)
{
queryString = queryString + " LIMIT "+skipNumber+", "+topNumber;
}
else
{
throw new ODataApplicationException("Invalid value for $skip", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
}
}
else
{
queryString = queryString + " LIMIT " + topNumber;
}
}
else
{
throw new ODataApplicationException("Invalid value for $top", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
}
}
LOG.info("SQL Query: "+queryString);
ResultSet resultSet = statement.executeQuery(queryString);
// special return logic for $count
if (isCount && resultSet.next())
{
int size = resultSet.getInt("rowcount");
LOG.info("Size = "+size);
entCollection.setCount(size);
return entCollection;
}
String primaryFieldName = fields.get(0).getFieldName(); String primaryFieldName = fields.get(0).getFieldName();
@ -149,7 +239,7 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
} }
else if (field.getType().equals(EdmPrimitiveTypeKind.DateTimeOffset.getFullQualifiedName())) else if (field.getType().equals(EdmPrimitiveTypeKind.DateTimeOffset.getFullQualifiedName()))
{ {
value = resultSet.getDate(fieldName); value = resultSet.getTimestamp(fieldName);
} }
else else
{ {
@ -167,10 +257,10 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
} catch (Exception e) { } catch (Exception e) {
LOG.error("Server Error occurred in reading "+this.resourceInfo.getResourceName(), e); LOG.error("Server Error occurred in reading "+this.resourceInfo.getResourceName(), e);
return lookupsCollection; return entCollection;
} }
return lookupsCollection; return entCollection;
} }
private URI createId(String entitySetName, Object id) { private URI createId(String entitySetName, Object id) {

View File

@ -0,0 +1,172 @@
package org.reso.service.data.meta;
import org.apache.olingo.commons.api.edm.EdmEnumType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitor;
import org.apache.olingo.server.api.uri.queryoption.expression.Literal;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
/**
* $filter
*/
public class FilterExpressionVisitor implements ExpressionVisitor<String> {
private static final Logger LOG = LoggerFactory.getLogger(FilterExpressionVisitor.class);
private static final Map<BinaryOperatorKind, String> BINARY_OPERATORS = new HashMap<BinaryOperatorKind, String>() {{
put(BinaryOperatorKind.ADD, " + ");
put(BinaryOperatorKind.AND, " AND ");
put(BinaryOperatorKind.DIV, " / ");
put(BinaryOperatorKind.EQ, " = ");
put(BinaryOperatorKind.GE, " >= ");
put(BinaryOperatorKind.GT, " > ");
put(BinaryOperatorKind.LE, " <= ");
put(BinaryOperatorKind.LT, " < ");
put(BinaryOperatorKind.MOD, " % ");
put(BinaryOperatorKind.MUL, " * ");
put(BinaryOperatorKind.NE, " <> ");
put(BinaryOperatorKind.OR, " OR ");
put(BinaryOperatorKind.SUB, " - ");
}};
private String entityAlias;
private ResourceInfo resourceInfo;
public FilterExpressionVisitor(ResourceInfo resourceInfo) {
this.entityAlias = resourceInfo.getTableName();
this.resourceInfo = resourceInfo;
}
@Override
public String visitBinaryOperator(BinaryOperatorKind operator, String left, String right)
throws ExpressionVisitException, ODataApplicationException {
String strOperator = BINARY_OPERATORS.get(operator);
if (strOperator == null) {
throw new ODataApplicationException("Unsupported binary operation: " + operator.name(),
operator == BinaryOperatorKind.HAS ?
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode() :
HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ENGLISH);
}
return left + strOperator + right;
}
@Override
public String visitUnaryOperator(UnaryOperatorKind operator, String operand)
throws ExpressionVisitException, ODataApplicationException {
switch (operator) {
case NOT:
return "NOT " + operand;
case MINUS:
return "-" + operand;
}
throw new ODataApplicationException("Wrong unary operator: " + operator,
HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ENGLISH);
}
@Override
public String visitMethodCall(MethodKind methodCall, List<String> parameters)
throws ExpressionVisitException, ODataApplicationException {
if (parameters.isEmpty() && methodCall.equals(MethodKind.NOW)) {
return "CURRENT_DATE";
}
String firsEntityParam = parameters.get(0);
switch (methodCall) {
case CONTAINS:
return firsEntityParam + " LIKE '%" + extractFromStringValue(parameters.get(1)) + "%'";
case STARTSWITH:
return firsEntityParam + " LIKE '" + extractFromStringValue(parameters.get(1)) + "%'";
case ENDSWITH:
return firsEntityParam + " LIKE '%" + extractFromStringValue(parameters.get(1));
case DAY:
return "DAY(" + firsEntityParam + ")";
case MONTH:
return "MONTH(" + firsEntityParam + ")";
case YEAR:
return "YEAR(" + firsEntityParam + ")";
}
throw new ODataApplicationException("Method call " + methodCall + " not implemented",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
}
@Override
public String visitLiteral(Literal literal) throws ExpressionVisitException, ODataApplicationException {
String literalAsString = literal.getText();
if (literal.getType() == null) {
literalAsString = "NULL";
}
if (literal.getType().getFullQualifiedName().equals( EdmPrimitiveTypeKind.DateTimeOffset.getFullQualifiedName() ) )
{
return "'"+literalAsString+"'";
}
return literalAsString;
}
@Override
public String visitMember(Member member) throws ExpressionVisitException, ODataApplicationException {
List<UriResource> resources = member.getResourcePath().getUriResourceParts();
UriResource first = resources.get(0);
// TODO: Enum and ComplexType; joins
if (resources.size() == 1 && first instanceof UriResourcePrimitiveProperty) {
UriResourcePrimitiveProperty primitiveProperty = (UriResourcePrimitiveProperty) first;
return entityAlias + "." + primitiveProperty.getProperty().getName();
} else {
throw new ODataApplicationException("Only primitive properties are implemented in filter expressions",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
}
}
@Override
public String visitEnum(EdmEnumType type, List<String> enumValues)
throws ExpressionVisitException, ODataApplicationException {
throw new ODataApplicationException("Enums are not implemented", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(),
Locale.ENGLISH);
}
@Override
public String visitLambdaExpression(String lambdaFunction, String lambdaVariable, Expression expression)
throws ExpressionVisitException, ODataApplicationException {
throw new ODataApplicationException("Lambda expressions are not implemented",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
}
@Override
public String visitAlias(String aliasName) throws ExpressionVisitException, ODataApplicationException {
throw new ODataApplicationException("Aliases are not implemented",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
}
@Override
public String visitTypeLiteral(EdmType type) throws ExpressionVisitException, ODataApplicationException {
throw new ODataApplicationException("Type literals are not implemented",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
}
@Override
public String visitLambdaReference(String variableName) throws ExpressionVisitException, ODataApplicationException {
throw new ODataApplicationException("Lambda references are not implemented",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
}
private String extractFromStringValue(String val) {
return val.substring(1, val.length() - 1);
}
}

View File

@ -23,7 +23,7 @@ public class BasicAuthProvider implements Provider
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthProvider.class); private static final Logger LOG = LoggerFactory.getLogger(BasicAuthProvider.class);
/** /**
* A simple BEARER Token Auth with a set token. * A simple BASIC Auth with static username and password.
* @param req The HTTP Request object from the servlet. * @param req The HTTP Request object from the servlet.
* @return true if authorized, false otherwise. * @return true if authorized, false otherwise.
*/ */
@ -38,7 +38,6 @@ public class BasicAuthProvider implements Provider
if (authResp!=null && authResp.length()>0) if (authResp!=null && authResp.length()>0)
{ {
String[] parts = authResp.split(BasicAuthProvider.AUTH_SPACE); String[] parts = authResp.split(BasicAuthProvider.AUTH_SPACE);
LOG.info("header:"+authResp);
if (parts[0].equals(BasicAuthProvider.BASIC_STR) && parts.length==2) if (parts[0].equals(BasicAuthProvider.BASIC_STR) && parts.length==2)
{ {
String base64decoded = new String(Base64.getDecoder().decode(parts[1])); String base64decoded = new String(Base64.getDecoder().decode(parts[1]));
@ -48,8 +47,6 @@ public class BasicAuthProvider implements Provider
{ {
String username = parts[0]; String username = parts[0];
String password = parts[1]; String password = parts[1];
LOG.info("User:"+username);
LOG.info("Pass:"+password);
if (username.equals(AUTH_USER) && password.equals(AUTH_PASSWORD)) if (username.equals(AUTH_USER) && password.equals(AUTH_PASSWORD))
{ {

View File

@ -20,7 +20,7 @@ public class BearerAuthProvider implements Provider
private static final Logger LOG = LoggerFactory.getLogger(BearerAuthProvider.class); private static final Logger LOG = LoggerFactory.getLogger(BearerAuthProvider.class);
/** /**
* A simple BASIC Auth with static username and password. Purely for testing purposes. * A simple BEARER Token Auth with a set token.
* @param req The HTTP Request object from the servlet. * @param req The HTTP Request object from the servlet.
* @return true if authorized, false otherwise. * @return true if authorized, false otherwise.
*/ */

View File

@ -49,7 +49,7 @@ public class RESOservlet extends HttpServlet
} }
this.validator = new Validator(); this.validator = new Validator();
// this.validator.addProvider(new BasicAuthProvider()); // this.validator.addProvider(new BasicAuthProvider()); // We're using this for the token auth. Only use here for easy browser testing.
this.validator.addProvider(new BearerAuthProvider()); this.validator.addProvider(new BearerAuthProvider());
String mysqlHost = env.get("SQL_HOST"); String mysqlHost = env.get("SQL_HOST");