From 0e6c9a118366a0397594da74087b5812b6f52beb Mon Sep 17 00:00:00 2001 From: Christian Holzer Date: Thu, 19 Feb 2015 17:11:45 +0100 Subject: [PATCH] [OLINGO-545] OrderBy and Filter System Query Option evaluation added to TecScenario Signed-off-by: Christian Amend --- .../olingo/fit/AbstractBaseTestITCase.java | 18 +- .../fit/CXFOAuth2HttpClientFactory.java | 7 +- .../client/FilterSystemQueryITCase.java | 976 ++++++++++++++++++ .../client/OrderBySystemQueryITCase.java | 172 +++ .../client/core/uri/AbstractURIBuilder.java | 1 - .../processor/TechnicalEntityProcessor.java | 33 +- .../tecsvc/processor/TechnicalProcessor.java | 2 - .../expression/ExpressionVisitorImpl.java | 236 +++++ .../expression/FilterRuntimeException.java | 38 + .../expression/FilterSystemQueryHandler.java | 135 +++ .../expression/operand/TypedOperand.java | 199 ++++ .../expression/operand/UntypedOperand.java | 155 +++ .../expression/operand/VisitorOperand.java | 93 ++ .../expression/operation/BinaryOperator.java | 318 ++++++ .../operation/MethodCallOperator.java | 334 ++++++ .../expression/operation/UnaryOperator.java | 63 ++ .../expression/primitive/EdmNull.java | 58 ++ 17 files changed, 2812 insertions(+), 26 deletions(-) create mode 100644 fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java create mode 100644 fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/ExpressionVisitorImpl.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterRuntimeException.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterSystemQueryHandler.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/TypedOperand.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/UntypedOperand.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/VisitorOperand.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/BinaryOperator.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/MethodCallOperator.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/UnaryOperator.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/primitive/EdmNull.java diff --git a/fit/src/test/java/org/apache/olingo/fit/AbstractBaseTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/AbstractBaseTestITCase.java index 21dcce1be..54658ae88 100644 --- a/fit/src/test/java/org/apache/olingo/fit/AbstractBaseTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/AbstractBaseTestITCase.java @@ -18,6 +18,15 @@ */ package org.apache.olingo.fit; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.apache.catalina.LifecycleException; import org.apache.commons.io.IOUtils; import org.apache.olingo.client.api.CommonODataClient; @@ -34,15 +43,6 @@ import org.junit.BeforeClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; - public abstract class AbstractBaseTestITCase { /** diff --git a/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpClientFactory.java b/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpClientFactory.java index d35b6b04e..3a7250889 100644 --- a/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpClientFactory.java +++ b/fit/src/test/java/org/apache/olingo/fit/CXFOAuth2HttpClientFactory.java @@ -18,11 +18,11 @@ */ package org.apache.olingo.fit; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; import java.io.IOException; import java.net.URI; + import javax.ws.rs.core.MediaType; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean; @@ -50,6 +50,9 @@ import org.apache.olingo.client.core.http.AbstractOAuth2HttpClientFactory; import org.apache.olingo.client.core.http.OAuth2Exception; import org.apache.olingo.fit.rest.OAuth2Provider; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + public class CXFOAuth2HttpClientFactory extends AbstractOAuth2HttpClientFactory { private static final OAuthClientUtils.Consumer OAUTH2_CONSUMER = diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java new file mode 100644 index 000000000..65da7950b --- /dev/null +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java @@ -0,0 +1,976 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.fit.tecsvc.client; + +import static org.junit.Assert.assertEquals; + +import java.net.URI; + +import org.apache.olingo.client.api.ODataClient; +import org.apache.olingo.client.api.communication.ODataClientErrorException; +import org.apache.olingo.client.api.communication.request.cud.ODataEntityCreateRequest; +import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetRequest; +import org.apache.olingo.client.api.communication.response.ODataEntityCreateResponse; +import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; +import org.apache.olingo.client.core.ODataClientFactory; +import org.apache.olingo.commons.api.domain.ODataEntity; +import org.apache.olingo.commons.api.domain.ODataEntitySet; +import org.apache.olingo.commons.api.domain.ODataObjectFactory; +import org.apache.olingo.commons.api.domain.ODataValuable; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.fit.AbstractBaseTestITCase; +import org.apache.olingo.fit.tecsvc.TecSvcConst; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +public class FilterSystemQueryITCase extends AbstractBaseTestITCase { + + private static final String ES_COMP_ALL_PRIM = "ESCompAllPrim"; + private static final String SERVICE_URI = TecSvcConst.BASE_URI; + private static final String ES_TWO_KEY_NAV = "ESTwoKeyNav"; + private static final String ES_ALL_PRIM = "ESAllPrim"; + + @AfterClass + public static void tearDownAfterClass() throws Exception {} + + @Test + public void testTimeOfDayLiteral() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "PropertyTimeOfDay eq 03:26:05"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDateLiteral() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "PropertyDate eq 2012-12-03"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDateTimeOffsetLiteral() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "PropertyDateTimeOffset eq 2012-12-03T07:16:23Z"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testInt64Literal() { + long value = Integer.MAX_VALUE + 1L; + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "PropertyInt64 gt " + value); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDoubleLiteral() { + Double value = -17900000000000000000.0; + + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "PropertyDouble le " + value.toString()); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testSimpleEq() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq 1"); + + assertEquals(2, result.getBody().getEntities().size()); + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testBinaryIntegerOperations() { + ODataRetrieveResponse result = + sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 add 1 eq (1 sub 3) div 2 mul 3 add 7"); + + assertEquals(1, result.getBody().getEntities().size()); + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testClientEscaping() { + final ODataClient client = getClient(); + final String filter = client.getFilterFactory().eq( + client.getFilterFactory().getArgFactory().property("PropertyString"), + client.getFilterFactory().getArgFactory().literal("First Resource - positive values")).build(); + + final URI uri = client.newURIBuilder(SERVICE_URI) + .appendEntitySetSegment(ES_ALL_PRIM) + .filter(filter) + .build(); + + final ODataRetrieveResponse response = client.getRetrieveRequestFactory() + .getEntitySetRequest(uri).execute(); + + assertEquals(1, response.getBody().getEntities().size()); + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testStringProperty() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyString eq '2'"); + + assertEquals(1, result.getBody().getEntities().size()); + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testBooleanOperator() { + ODataRetrieveResponse result = + sendRequest(ES_TWO_KEY_NAV, "PropertyString eq '2' and PropertyInt16 eq 1"); + assertEquals(1, result.getBody().getEntities().size()); + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyString eq '2' or PropertyInt16 eq 1"); + assertEquals(2, result.getBody().getEntities().size()); + oDataEntity = result.getBody().getEntities().get(0); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testBooleanOperatorWithNull() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq null"); + assertEquals(0, result.getBody().getEntities().size()); + + result = sendRequest(ES_TWO_KEY_NAV, "null eq null"); + assertEquals(4, result.getBody().getEntities().size()); + + result = sendRequest(ES_TWO_KEY_NAV, "null ne null"); + assertEquals(0, result.getBody().getEntities().size()); + } + + @Test + public void testUnaryWithNullLiterals() { + ODataRetrieveResponse result = + sendRequest(ES_TWO_KEY_NAV, "PropertyComp/PropertyComp/PropertyBoolean eq not null"); + assertEquals(0, result.getBody().getEntities().size()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyComp/PropertyComp/PropertyBoolean eq 0 add -(5 add null)"); + assertEquals(0, result.getBody().getEntities().size()); + } + + @Test + public void testUnaryWithWrongTypes() { + fail(ES_ALL_PRIM, "PropertyInt16 eq 6 add - 'test'", HttpStatusCode.BAD_REQUEST); + fail(ES_ALL_PRIM, "PropertyBoolean eq not 'test'", HttpStatusCode.BAD_REQUEST); + } + + @Test + public void testMethodCallsWithNull() { + // One representative of "stringFuntion" "residue class" + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "endswith(PropertyString, null) eq null"); // null eq null => true + assertEquals(3, result.getBody().getEntities().size()); + + // One representative of "stringifiedValueFunction" "residue class" + result = sendRequest(ES_ALL_PRIM, "substring(PropertyString, null) eq null"); // null eq null => true + assertEquals(3, result.getBody().getEntities().size()); + + // Substring + result = sendRequest(ES_ALL_PRIM, "hour(null) eq null"); // null eq null => true + assertEquals(3, result.getBody().getEntities().size()); + + result = sendRequest(ES_ALL_PRIM, "substring(PropertyString, 0, null) eq null"); // null eq null => true + assertEquals(3, result.getBody().getEntities().size()); + } + + @Test + public void testUnknownLiteral() { + // Check if the error code is equals to 400 + fail(ES_ALL_PRIM, "PropertyInt16 eq ThisIsNotAValidLiteral", HttpStatusCode.BAD_REQUEST); + } + + @Test + public void testErrorCodeArithmetic() { + fail(ES_ALL_PRIM, "PropertyInt16 eq 'hey' add 5", HttpStatusCode.BAD_REQUEST); + fail(ES_ALL_PRIM, "PropertyDate eq 5.0 add 2012-12-03", HttpStatusCode.BAD_REQUEST); + fail(ES_ALL_PRIM, "PropertyDouble mod 5 eq 0", HttpStatusCode.BAD_REQUEST); + fail(ES_ALL_PRIM, "UnkownProperty eq null", HttpStatusCode.BAD_REQUEST); + } + + @Test + public void testNumericBinaryOperationWithNullValues() { + // Create new Entries + final String filterString = "PropertyString eq null"; + + ODataClient client = getClient(); + ODataObjectFactory objectFactory = client.getObjectFactory(); + + ODataEntity entity = objectFactory.newEntity(new FullQualifiedName("olingo.odata.test1.ETAllPrim")); + + entity.getProperties().add( + objectFactory.newPrimitiveProperty("PropertyInt16", objectFactory.newPrimitiveValueBuilder() + .buildInt16((short) 1))); + + final URI uri = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim").build(); + ODataEntityCreateResponse createResponse = + client.getCUDRequestFactory().getEntityCreateRequest(uri, entity).execute(); + + final URI receiveURI = + client.newURIBuilder(SERVICE_URI) + .appendEntitySetSegment("ESAllPrim") + .filter(filterString) + .build(); + + ODataEntitySetRequest filterRequest = + client.getRetrieveRequestFactory().getEntitySetRequest(receiveURI); + filterRequest.addCustomHeader(HttpHeader.COOKIE, createResponse.getHeader(HttpHeader.SET_COOKIE).iterator().next()); + ODataRetrieveResponse filterResponse = filterRequest.execute(); + + assertEquals(1, filterResponse.getBody().getEntities().size()); + } + + @Test + public void testNumericComparisionOperators() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 ge 1"); + assertEquals(4, result.getBody().getEntities().size()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 gt 1"); + assertEquals(2, result.getBody().getEntities().size()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 lt 2"); + assertEquals(2, result.getBody().getEntities().size()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 le 2"); + assertEquals(3, result.getBody().getEntities().size()); + + result = sendRequest(ES_ALL_PRIM, "PropertyDouble ge -179000"); + assertEquals(2, result.getBody().getEntities().size()); + + result = sendRequest(ES_ALL_PRIM, "PropertyDouble gt -179000"); + assertEquals(1, result.getBody().getEntities().size()); + + result = sendRequest(ES_ALL_PRIM, "PropertyDouble lt -179000"); + assertEquals(1, result.getBody().getEntities().size()); + + result = sendRequest(ES_ALL_PRIM, "PropertyDouble le -179000"); + assertEquals(2, result.getBody().getEntities().size()); + } + + @Test + public void testBinaryOperationIntegerDecimalWithPromotion() { + String filterString = "" + + "PropertyInt16 mod 2 eq " // Choose mod 2 == 1 => { 1, 3, .. } + + "(((5 sub 1) div 5) " // Integer Division 4 / 5 == 0 + + "add 1) " // 0 + 1 = 1 + + "and " + + "PropertyComp/PropertyInt16 eq " // Complex Property + + "5.5 mul 2"; // Single * Int16 => Single => Int16 eq Single => Single eq Single + + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, filterString); + assertEquals(3, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + assertEquals("11", ((ODataValuable) ((ODataValuable) oDataEntity.getProperty("PropertyComp")).getComplexValue() + .get("PropertyInt16")).getValue() + .toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + assertEquals("11", ((ODataValuable) ((ODataValuable) oDataEntity.getProperty("PropertyComp")).getComplexValue() + .get("PropertyInt16")).getValue() + .toString()); + + oDataEntity = result.getBody().getEntities().get(2); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + assertEquals("11", ((ODataValuable) ((ODataValuable) oDataEntity.getProperty("PropertyComp")).getComplexValue() + .get("PropertyInt16")).getValue() + .toString()); + } + + @Test + public void testNotOperator() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "not (PropertyInt16 eq 1)"); + assertEquals(2, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testUnaryMinusOperator() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 gt -2 add - -3"); + assertEquals(2, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testUnaryMinusOperatorDecimal() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 gt -2.0 add - -3.0"); + assertEquals(2, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testStringPropertyEqualsNull() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyString eq null"); + assertEquals(0, result.getBody().getEntities().size()); + } + + @Test + public void testAddNullLiteral() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 add null eq 1"); + assertEquals(0, result.getBody().getEntities().size()); + } + + @Test + public void testAddNullLiteralEqualsNull() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 add null eq null"); + assertEquals(4, result.getBody().getEntities().size()); + } + + @Test + public void testSubstringStartAndEndGiven() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "substring(PropertyString, length('First') add 1, 8) eq ('Resource')"); + + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testSubstringStartGiven() { + ODataRetrieveResponse result = + sendRequest(ES_TWO_KEY_NAV, "substring(PropertyComp/PropertyComp/PropertyString, 6) eq 'Value'"); + + assertEquals(4, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(2); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(3); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + @SuppressWarnings("unused") + public void testSubstringDouble() { + try { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "substring(PropertyString, length('First')" + + "add 1, 2.0 * 4) eq ('Resource')"); + } catch (ODataClientErrorException e) { + assertEquals(400, e.getStatusLine().getStatusCode()); + } + } + + @Test + public void testYearFunctionDate() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "year(PropertyDate) eq 2015"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testYearFunctionDateTimeOffset() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "year(PropertyDateTimeOffset) eq 2012"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testMonthFunctionDateTimeOffset() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "month(PropertyDateTimeOffset) eq 12"); + assertEquals(3, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(2); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testMonthFunctionDate() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "month(PropertyDate) eq 11"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDayFunctionDateTimeOffset() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "day(PropertyDateTimeOffset) eq 3"); + assertEquals(3, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(2); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDayFunctionDate() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "day(PropertyDate) eq 5"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testHourFunctionDateTimeOffset() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "hour(PropertyDateTimeOffset) eq 7"); + assertEquals(2, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testHourFuntionTimeOfDay() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "hour(PropertyTimeOfDay) eq 3"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testMinuteFunctionDateTimeOffset() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "minute(PropertyDateTimeOffset) eq 17"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testMinuteFuntionTimeOfDay() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "minute(PropertyTimeOfDay) eq 49"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testSecondFunctionDateTimeOffset() { + ODataRetrieveResponse response = sendRequest(ES_ALL_PRIM, "second(PropertyDateTimeOffset) eq 8"); + assertEquals(1, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testSecondFuntionTimeOfDay() { + ODataRetrieveResponse response = sendRequest(ES_ALL_PRIM, "second(PropertyTimeOfDay) eq 14"); + assertEquals(1, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testFractionalsecondsDateTimeOffset() { + ODataRetrieveResponse response = + sendRequest(ES_COMP_ALL_PRIM, "fractionalseconds(PropertyComp/PropertyDateTimeOffset) eq 0.1234567"); + assertEquals(2, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(1); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testFractionalsecondsDateOfTime() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "fractionalseconds(PropertyTimeOfDay) eq 0"); + assertEquals(3, response.getBody().getEntities().size()); + } + + @Test + public void testDateTimeFunctionsNull() { + ODataRetrieveResponse response; + + response = sendRequest(ES_ALL_PRIM, "year(null) eq null"); + assertEquals(3, response.getBody().getEntities().size()); + + response = sendRequest(ES_ALL_PRIM, "month(null) eq null"); + assertEquals(3, response.getBody().getEntities().size()); + + response = sendRequest(ES_ALL_PRIM, "day(null) eq null"); + assertEquals(3, response.getBody().getEntities().size()); + + response = sendRequest(ES_ALL_PRIM, "hour(null) eq null"); + assertEquals(3, response.getBody().getEntities().size()); + + response = sendRequest(ES_ALL_PRIM, "minute(null) eq null"); + assertEquals(3, response.getBody().getEntities().size()); + + response = sendRequest(ES_ALL_PRIM, "second(null) eq null"); + assertEquals(3, response.getBody().getEntities().size()); + } + + @Test + public void testFloor() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq floor(3.8)"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq floor(3.1)"); + assertEquals(1, result.getBody().getEntities().size()); + + oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testCeiling() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq ceiling(2.1)"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq ceiling(2.6)"); + assertEquals(1, result.getBody().getEntities().size()); + + oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testRound() { + ODataRetrieveResponse result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq round(2.5)"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq round(2.4)"); + assertEquals(1, result.getBody().getEntities().size()); + + oDataEntity = result.getBody().getEntities().get(0); + assertEquals("2", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq round(2.6)"); + assertEquals(1, result.getBody().getEntities().size()); + + oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + + result = sendRequest(ES_TWO_KEY_NAV, "PropertyInt16 eq round(3.1)"); + assertEquals(1, result.getBody().getEntities().size()); + + oDataEntity = result.getBody().getEntities().get(0); + assertEquals("3", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + assertEquals("1", ((ODataValuable) oDataEntity.getProperty("PropertyString")).getValue().toString()); + } + + @Test + public void testEndsWith() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "endswith(PropertyString, 'values')"); + assertEquals(2, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = result.getBody().getEntities().get(1); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testIndexOf() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "indexof(PropertyString, 'positive') eq 17"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testStartsWith() { + ODataRetrieveResponse result = sendRequest(ES_ALL_PRIM, "startswith(PropertyString, 'First')"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testToLower() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "contains(PropertyString, tolower('POSITIVE'))"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testToUpper() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "contains(PropertyString, concat(toupper('f'), 'irst'))"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testTrim() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "trim(substring(PropertyString, 0, 6)) eq 'First'"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDecimalDiv() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "PropertyDouble eq 0 sub (358000 div 2)"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testNumericPromotionToInt64() { + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "PropertyInt64 eq 0"); + assertEquals(1, result.getBody().getEntities().size()); + + ODataEntity oDataEntity = result.getBody().getEntities().get(0); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void castEdm64ToDouble() { + double value = Float.MAX_VALUE + 1; + ODataRetrieveResponse result = + sendRequest(ES_ALL_PRIM, "PropertyInt64 lt " + value); + assertEquals(3, result.getBody().getEntities().size()); + } + + @Test + public void testDateTimeOffsetAddDuraton() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDateTimeOffset eq 2012-12-03T07:16:19Z add duration'PT4S'"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDurrationAddDuration() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDuration eq duration'PT2S' add duration'PT4S'"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDurrationLiteral() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDuration eq duration'P1DT'"); + assertEquals(0, response.getBody().getEntities().size()); + } + + @Test + public void testDateAddDuration() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDateTimeOffset eq 2012-12-02 add duration'P1DT7H16M23S'"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDateTimeOffsetSubDuration() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDateTimeOffset eq 2012-12-03T07:16:27Z sub duration'PT4S'"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDurrationSubDuration() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDuration sub duration'PT2S' eq duration'PT4S'"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDateSubDuration() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDateTimeOffset eq 2012-12-04 sub duration'P0DT16H43M37S'"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDateSubDate() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDuration eq 2012-12-04 sub 2012-12-04"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testDateTimeOffsetSubDateTimeOffset() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "PropertyDuration eq 2005-12-03T00:00:00Z sub 2005-12-03T00:00:00Z"); + assertEquals(1, response.getBody().getEntities().size()); + + final ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testNumericPromotion() { + /* + * The idea is use the largest possible number of a specific type and add a another number to force an + * implicit conversion to an higher type + */ + ODataRetrieveResponse response; + + // SByte => Int16 + byte byteValue = Byte.MAX_VALUE; // 2 ^ 7 -1 = 127 + response = sendRequest(ES_ALL_PRIM, "PropertyInt32 eq " + byteValue + " add " + 1); + assertEquals(0, response.getBody().getEntities().size()); // No error occurs + + // Int16 => Int32 + short shortValue = Short.MAX_VALUE; + response = sendRequest(ES_ALL_PRIM, "PropertyInt32 eq " + shortValue + " add " + 1); + assertEquals(0, response.getBody().getEntities().size()); // No error occurs + + // Int32 => Int64 + int intValue = Integer.MAX_VALUE; + response = sendRequest(ES_ALL_PRIM, "PropertyInt32 eq " + intValue + " add " + 1); + assertEquals(0, response.getBody().getEntities().size()); // No error occurs + + // Int64 => Double + Long longValue = Long.MAX_VALUE; + response = sendRequest(ES_ALL_PRIM, "PropertyInt32 eq " + longValue + " add " + 1); + assertEquals(0, response.getBody().getEntities().size()); // No error occurs + + // Single => Double + Float floatValue = Float.MAX_VALUE; + response = sendRequest(ES_ALL_PRIM, "PropertyInt32 eq " + floatValue.toString() + " add " + 1.0); + assertEquals(0, response.getBody().getEntities().size()); // No error occurs + } + + @Test + public void testNullComplexProperty() { + // Create a new entry.The complex property PropertyCompComp is set to null. So the structure of the property + // is still there, but filled is null value (primitive types) + // We define a filter, which returns all entry where PropertyCompComp/PropertyComp/PropertyInt16 is equals to 1 + + final ODataClient client = getClient(); + final ODataObjectFactory factory = client.getObjectFactory(); + ODataEntity newEntity = factory.newEntity(new FullQualifiedName("olingo.odata.test1", "ETKeyNav")); + newEntity.getProperties().add(factory.newComplexProperty("PropertyCompComp", null)); + newEntity.getProperties().add(factory.newPrimitiveProperty("PropertyInt16", + factory.newPrimitiveValueBuilder().buildInt16((short) 4))); + newEntity.getProperties().add(factory.newPrimitiveProperty("PropertyString", + factory.newPrimitiveValueBuilder().buildString("Test"))); + newEntity.getProperties().add( + factory.newComplexProperty("PropertyCompAllPrim", + factory.newComplexValue("CTAllPrim") + .add(factory.newPrimitiveProperty( + "PropertyString", + factory.newPrimitiveValueBuilder().buildString("Test 3"))))); + + newEntity.getProperties().add( + factory.newComplexProperty("PropertyCompTwoPrim", + factory.newComplexValue("CTTwoPrim") + .add(factory.newPrimitiveProperty( + "PropertyInt16", + factory.newPrimitiveValueBuilder().buildInt16((short) 1))) + .add(factory.newPrimitiveProperty( + "PropertyString", + factory.newPrimitiveValueBuilder().buildString("Test2"))))); + + final URI uri = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESKeyNav").build(); + ODataEntityCreateRequest request = + client.getCUDRequestFactory().getEntityCreateRequest(uri, newEntity); + ODataEntityCreateResponse response = request.execute(); + assertEquals(HttpStatusCode.CREATED.getStatusCode(), response.getStatusCode()); + + final String cookie = response.getHeader(HttpHeader.SET_COOKIE).iterator().next(); + + // Do the filter request + ODataRetrieveResponse result = + sendRequest("ESKeyNav", "PropertyCompComp/PropertyComp/PropertyInt16 eq 1", cookie); + assertEquals(3, result.getBody().getEntities().size()); + + // Try filter all entries where PropertyCompComp is null + result = sendRequest("ESKeyNav", "PropertyCompComp/PropertyComp/PropertyInt16 eq null", cookie); + assertEquals(1, result.getBody().getEntities().size()); + } + + @Test + public void testSringFunctionWithoutStringParameters() { + fail("ESServerSidePaging", "filter=contains(PropertyInt16, 3) eq 'hallo'", HttpStatusCode.BAD_REQUEST); + } + + private ODataRetrieveResponse sendRequest(String entitySet, String filterString) { + return sendRequest(entitySet, filterString, null); + } + + private ODataRetrieveResponse sendRequest(String entitySet, String filterString, String cookie) { + final ODataClient client = getClient(); + + final URI uri = + client.newURIBuilder(SERVICE_URI) + .appendEntitySetSegment(entitySet) + .filter(filterString) + .build(); + + ODataEntitySetRequest request = client.getRetrieveRequestFactory().getEntitySetRequest(uri); + if (cookie != null) { + request.addCustomHeader(HttpHeader.COOKIE, cookie); + } + + return request.execute(); + } + + private void fail(String entitySet, String filterString, HttpStatusCode errorCode) { + try { + sendRequest(entitySet, filterString); + Assert.fail(); + } catch (ODataClientErrorException e) { + assertEquals(errorCode.getStatusCode(), e.getStatusLine().getStatusCode()); + } + } + + @Override + protected ODataClient getClient() { + ODataClient odata = ODataClientFactory.getV4(); + odata.getConfiguration().setDefaultPubFormat(ODataFormat.JSON); + return odata; + } + +} diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java new file mode 100644 index 000000000..21d248c89 --- /dev/null +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.fit.tecsvc.client; + +import static org.junit.Assert.assertEquals; + +import java.net.URI; + +import org.apache.olingo.client.api.ODataClient; +import org.apache.olingo.client.api.communication.ODataClientErrorException; +import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetRequest; +import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; +import org.apache.olingo.client.core.ODataClientFactory; +import org.apache.olingo.commons.api.domain.ODataEntity; +import org.apache.olingo.commons.api.domain.ODataEntitySet; +import org.apache.olingo.commons.api.domain.ODataValuable; +import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.fit.AbstractBaseTestITCase; +import org.apache.olingo.fit.tecsvc.TecSvcConst; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +public class OrderBySystemQueryITCase extends AbstractBaseTestITCase { + + private static final String ES_TWO_PRIM = "ESTwoPrim"; + private static final String ES_ALL_PRIM = "ESAllPrim"; + + private static final String SERVICE_URI = TecSvcConst.BASE_URI; + + @AfterClass + public static void tearDownAfterClass() throws Exception {} + + @Test + public void testSimpleOrderBy() { + ODataRetrieveResponse response = null; + + response = sendRequest(ES_ALL_PRIM, "PropertyDate"); + assertEquals(3, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(1); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(2); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testSimpleOrderByDecending() { + ODataRetrieveResponse response = null; + + response = sendRequest(ES_ALL_PRIM, "PropertyDate desc"); + assertEquals(3, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(1); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(2); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testMultipleOrderBy() { + final ODataRetrieveResponse response = sendRequest(ES_ALL_PRIM, "PropertyByte, PropertyInt16"); + assertEquals(3, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(1); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(2); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testMultipleOrderByDecending() { + final ODataRetrieveResponse response = sendRequest(ES_ALL_PRIM, "PropertyByte, PropertyInt16 desc"); + assertEquals(3, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("0", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(1); + assertEquals("-32768", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(2); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testOrderByWithNull() { + final ODataRetrieveResponse response = sendRequest(ES_TWO_PRIM, "PropertyString"); + assertEquals(4, response.getBody().getEntities().size()); + + ODataEntity oDataEntity = response.getBody().getEntities().get(0); + assertEquals("-32766", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(1); + assertEquals("32766", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(2); + assertEquals("-365", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + + oDataEntity = response.getBody().getEntities().get(3); + assertEquals("32767", ((ODataValuable) oDataEntity.getProperty("PropertyInt16")).getValue().toString()); + } + + @Test + public void testOrderByInvalidExpression() { + fail(ES_TWO_PRIM, "PropertyString add 10", HttpStatusCode.BAD_REQUEST); + } + + private ODataRetrieveResponse sendRequest(String entitySet, String orderByString) { + final ODataClient client = getClient(); + String escapedFilterString = escapeFilterString(orderByString); + + final URI uri = + client.newURIBuilder(SERVICE_URI) + .appendEntitySetSegment(entitySet) + .orderBy(escapedFilterString) + .build(); + + ODataEntitySetRequest request = client.getRetrieveRequestFactory().getEntitySetRequest(uri); + + return request.execute(); + } + + private void fail(String entitySet, String filterString, HttpStatusCode errorCode) { + try { + sendRequest(entitySet, filterString); + Assert.fail(); + } catch (ODataClientErrorException e) { + assertEquals(errorCode.getStatusCode(), e.getStatusLine().getStatusCode()); + } + } + + private String escapeFilterString(String filterString) { + return filterString.replace(" ", "%20"); + } + + @Override + protected ODataClient getClient() { + ODataClient odata = ODataClientFactory.getV4(); + odata.getConfiguration().setDefaultPubFormat(ODataFormat.JSON); + return odata; + } +} diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java index f80e39490..5802ff366 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java @@ -311,7 +311,6 @@ public abstract class AbstractURIBuilder> impleme // it will try to call URLEncodedUtils.format(Iterable<>,Charset) method, // which works in desktop java application, however, throws NoSuchMethodError in android OS, // so here manually construct the URL by its overload URLEncodedUtils.format(List<>,String). - //String queryStr = URLEncodedUtils.format(list1, "UTF-8"); final String queryStr = encodeQueryParameter(list1); sb.append(queryStr); } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java index 986836693..3113028e7 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java @@ -32,6 +32,7 @@ import org.apache.olingo.commons.api.http.HttpContentType; import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.commons.api.http.HttpMethod; import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.data.EntitySetImpl; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; @@ -52,13 +53,14 @@ import org.apache.olingo.server.api.uri.UriResourceEntitySet; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.SelectOption; import org.apache.olingo.server.tecsvc.data.DataProvider; +import org.apache.olingo.server.tecsvc.processor.expression.FilterSystemQueryHandler; /** * Technical Processor for entity-related functionality. */ public class TechnicalEntityProcessor extends TechnicalProcessor implements EntityCollectionProcessor, ActionEntityCollectionProcessor, CountEntityCollectionProcessor, - EntityProcessor, ActionEntityProcessor, MediaEntityProcessor { + EntityProcessor, ActionEntityProcessor, MediaEntityProcessor { public TechnicalEntityProcessor(final DataProvider dataProvider) { super(dataProvider); @@ -68,14 +70,21 @@ public class TechnicalEntityProcessor extends TechnicalProcessor public void readEntityCollection(final ODataRequest request, ODataResponse response, final UriInfo uriInfo, final ContentType requestedContentType) throws ODataApplicationException, SerializerException { validateOptions(uriInfo.asUriInfoResource()); - final EdmEntitySet edmEntitySet = getEdmEntitySet(uriInfo); - EntitySet entitySet = readEntityCollection(uriInfo); - if (entitySet == null) { + + final EdmEntitySet edmEntitySet = getEdmEntitySet(uriInfo.asUriInfoResource()); + final EntitySet entitySetInitial = readEntityCollection(uriInfo); + if (entitySetInitial == null) { throw new ODataApplicationException("Nothing found.", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT); } else { - if (uriInfo.getCountOption() != null && uriInfo.getCountOption().getValue()) { - setCount(entitySet); - } + // Modifying the original entitySet means modifying the "database", so we have to make a shallow + // copy of the entity set (new EntitySet, but exactly the same data) + EntitySet entitySet = new EntitySetImpl(); + entitySet.getEntities().addAll(entitySetInitial.getEntities()); + + // Apply system query options + FilterSystemQueryHandler.applyFilterSystemQuery(uriInfo.getFilterOption(), entitySet, edmEntitySet); + FilterSystemQueryHandler.applyOrderByOption(uriInfo.getOrderByOption(), entitySet, edmEntitySet); + final ODataFormat format = ODataFormat.fromContentType(requestedContentType); ODataSerializer serializer = odata.createSerializer(format); final ExpandOption expand = uriInfo.getExpandOption(); @@ -95,7 +104,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor @Override public void processActionEntityCollection(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo, final ContentType requestFormat, final ContentType responseFormat) - throws ODataApplicationException, DeserializerException, SerializerException { + throws ODataApplicationException, DeserializerException, SerializerException { throw new ODataApplicationException("Process entity collection is not supported yet.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); } @@ -150,7 +159,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor @Override public void createMediaEntity(final ODataRequest request, ODataResponse response, final UriInfo uriInfo, final ContentType requestFormat, final ContentType responseFormat) - throws ODataApplicationException, DeserializerException, SerializerException { + throws ODataApplicationException, DeserializerException, SerializerException { createEntity(request, response, uriInfo, requestFormat, responseFormat); } @@ -168,9 +177,9 @@ public class TechnicalEntityProcessor extends TechnicalProcessor final EdmEntityType edmEntityType = edmEntitySet.getEntityType(); Entity entity = dataProvider.create(edmEntitySet); - if (edmEntityType.hasStream()) { // called from createMediaEntity(...), not directly + if (edmEntityType.hasStream()) { // called from createMediaEntity(...), not directly dataProvider.setMedia(entity, odata.createFixedFormatDeserializer().binary(request.getBody()), - requestFormat.toContentTypeString()); + requestFormat.toContentTypeString()); } else { dataProvider.update(edmEntitySet, entity, odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) @@ -228,7 +237,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor @Override public void processActionEntity(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo, final ContentType requestFormat, final ContentType responseFormat) - throws ODataApplicationException, DeserializerException, SerializerException { + throws ODataApplicationException, DeserializerException, SerializerException { throw new ODataApplicationException("Process entity is not supported yet.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java index 3ecb4ed81..31257cbc7 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java @@ -156,9 +156,7 @@ public abstract class TechnicalProcessor implements Processor { protected void validateOptions(final UriInfoResource uriInfo) throws ODataApplicationException { if (uriInfo.getCountOption() != null || !uriInfo.getCustomQueryOptions().isEmpty() - || uriInfo.getFilterOption() != null || uriInfo.getIdOption() != null - || uriInfo.getOrderByOption() != null || uriInfo.getSearchOption() != null || uriInfo.getSkipOption() != null || uriInfo.getSkipTokenOption() != null diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/ExpressionVisitorImpl.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/ExpressionVisitorImpl.java new file mode 100644 index 000000000..12a4940a0 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/ExpressionVisitorImpl.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression; + +import java.util.List; +import java.util.Locale; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEnumType; +import org.apache.olingo.commons.api.edm.EdmProperty; +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.UriInfoResource; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourcePartTyped; +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.MethodKind; +import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind; +import org.apache.olingo.server.tecsvc.processor.expression.operand.TypedOperand; +import org.apache.olingo.server.tecsvc.processor.expression.operand.UntypedOperand; +import org.apache.olingo.server.tecsvc.processor.expression.operand.VisitorOperand; +import org.apache.olingo.server.tecsvc.processor.expression.operation.BinaryOperator; +import org.apache.olingo.server.tecsvc.processor.expression.operation.MethodCallOperator; +import org.apache.olingo.server.tecsvc.processor.expression.operation.UnaryOperator; + +public class ExpressionVisitorImpl implements ExpressionVisitor { + + final private Entity entity; + final private EdmEntitySet edmEntitySet; + + public ExpressionVisitorImpl(Entity entity, EdmEntitySet edmEntitySet) { + this.entity = entity; + this.edmEntitySet = edmEntitySet; + } + + @Override + public VisitorOperand visitBinaryOperator(BinaryOperatorKind operator, VisitorOperand left, VisitorOperand right) + throws ExpressionVisitException, ODataApplicationException { + + final BinaryOperator binaryOperator = new BinaryOperator(left, right); + + switch (operator) { + case AND: + return binaryOperator.andOperator(); + case OR: + return binaryOperator.orOperator(); + case EQ: + return binaryOperator.equalsOperator(); + case NE: + return binaryOperator.notEqualsOperator(); + case GE: + return binaryOperator.greaterEqualsOperator(); + case GT: + return binaryOperator.greaterThanOperator(); + case LE: + return binaryOperator.lessEqualsOperator(); + case LT: + return binaryOperator.lessThanOperator(); + case ADD: + case SUB: + case MUL: + case DIV: + case MOD: + return binaryOperator.arithmeticOperator(operator); + default: + return throwNotImplemented(); + } + } + + @Override + public VisitorOperand visitUnaryOperator(UnaryOperatorKind operator, VisitorOperand operand) + throws ExpressionVisitException, ODataApplicationException { + + final UnaryOperator unaryOperator = new UnaryOperator(operand); + + switch (operator) { + case MINUS: + return unaryOperator.minusOperation(); + case NOT: + return unaryOperator.notOperation(); + default: + // Can`t happen + return throwNotImplemented(); + } + } + + @Override + public VisitorOperand visitMethodCall(MethodKind methodCall, List parameters) + throws ExpressionVisitException, ODataApplicationException { + + final MethodCallOperator methodCallOperation = new MethodCallOperator(parameters); + + switch (methodCall) { + case ENDSWITH: + return methodCallOperation.endsWith(); + case INDEXOF: + return methodCallOperation.indexOf(); + case STARTSWITH: + return methodCallOperation.startsWith(); + case TOLOWER: + return methodCallOperation.toLower(); + case TOUPPER: + return methodCallOperation.toUpper(); + case TRIM: + return methodCallOperation.trim(); + case SUBSTRING: + return methodCallOperation.substring(); + case CONTAINS: + return methodCallOperation.contains(); + case CONCAT: + return methodCallOperation.concat(); + case LENGTH: + return methodCallOperation.length(); + case YEAR: + return methodCallOperation.year(); + case MONTH: + return methodCallOperation.month(); + case DAY: + return methodCallOperation.day(); + case HOUR: + return methodCallOperation.hour(); + case MINUTE: + return methodCallOperation.minute(); + case SECOND: + return methodCallOperation.second(); + case FRACTIONALSECONDS: + return methodCallOperation.fractionalseconds(); + case ROUND: + return methodCallOperation.round(); + case FLOOR: + return methodCallOperation.floor(); + case CEILING: + return methodCallOperation.ceiling(); + + default: + return throwNotImplemented(); + } + } + + @Override + public VisitorOperand visitLambdaExpression(String lambdaFunction, String lambdaVariable, Expression expression) + throws ExpressionVisitException, ODataApplicationException { + + return throwNotImplemented(); + } + + @Override + public VisitorOperand visitLiteral(String literal) throws ExpressionVisitException, ODataApplicationException { + + return new UntypedOperand(literal); + } + + @Override + public VisitorOperand visitMember(UriInfoResource member) throws ExpressionVisitException, + ODataApplicationException { + + final List uriResourceParts = member.getUriResourceParts(); + + // UriResourceParts contains at least one UriResource + Property currentProperty = entity.getProperty(uriResourceParts.get(0).toString()); + EdmType currentType = ((UriResourcePartTyped) uriResourceParts.get(0)).getType(); + + EdmProperty currentEdmProperty = edmEntitySet.getEntityType() + .getStructuralProperty(uriResourceParts.get(0).toString()); + + for (int i = 1; i < uriResourceParts.size(); i++) { + currentType = ((UriResourcePartTyped) uriResourceParts.get(i)).getType(); + + if (currentProperty.isComplex() || currentProperty.isLinkedComplex()) { + final List complex = currentProperty.isLinkedComplex() ? + currentProperty.asLinkedComplex().getValue() : currentProperty.asComplex(); + + for (final Property innerProperty : complex) { + if (innerProperty.getName().equals(uriResourceParts.get(i).toString())) { + EdmComplexType edmComplexType = (EdmComplexType) currentEdmProperty.getType(); + currentEdmProperty = edmComplexType.getStructuralProperty(uriResourceParts.get(i).toString()); + currentProperty = innerProperty; + break; + } + } + } + } + + return new TypedOperand(((Property) currentProperty).getValue(), currentType, currentEdmProperty); + } + + @Override + public VisitorOperand visitAlias(String aliasName) throws ExpressionVisitException, ODataApplicationException { + return throwNotImplemented(); + } + + @Override + public VisitorOperand visitTypeLiteral(EdmType type) throws ExpressionVisitException, ODataApplicationException { + return throwNotImplemented(); + } + + @Override + public VisitorOperand visitLambdaReference(String variableName) throws ExpressionVisitException, + ODataApplicationException { + return throwNotImplemented(); + } + + @Override + public VisitorOperand visitEnum(EdmEnumType type, List enumValues) throws ExpressionVisitException, + ODataApplicationException { + return throwNotImplemented(); + } + + private VisitorOperand throwNotImplemented() throws ODataApplicationException { + throw new ODataApplicationException("Not implemented", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), + Locale.ROOT); + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterRuntimeException.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterRuntimeException.java new file mode 100644 index 000000000..21fc11011 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterRuntimeException.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression; + +import org.apache.olingo.commons.api.ODataRuntimeException; + +public class FilterRuntimeException extends ODataRuntimeException { + + private static final long serialVersionUID = 1L; + + public FilterRuntimeException(Exception cause) { + super(cause); + } + + public FilterRuntimeException(String msg, Exception cause) { + super(msg, cause); + } + + public FilterRuntimeException(String msg) { + super(msg); + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterSystemQueryHandler.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterSystemQueryHandler.java new file mode 100644 index 000000000..5b5b506b0 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/FilterSystemQueryHandler.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Locale; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.EntitySet; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.edm.primitivetype.EdmBoolean; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.FilterOption; +import org.apache.olingo.server.api.uri.queryoption.OrderByItem; +import org.apache.olingo.server.api.uri.queryoption.OrderByOption; +import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; +import org.apache.olingo.server.tecsvc.processor.expression.operand.TypedOperand; +import org.apache.olingo.server.tecsvc.processor.expression.operand.VisitorOperand; + +public class FilterSystemQueryHandler { + + public static void applyFilterSystemQuery(FilterOption filterOption, EntitySet entitySet, EdmEntitySet edmEntitySet) + throws ODataApplicationException { + + if (filterOption == null) { + return; + } + + try { + final Iterator iter = entitySet.getEntities().iterator(); + + while (iter.hasNext()) { + final VisitorOperand operand = filterOption.getExpression() + .accept(new ExpressionVisitorImpl(iter.next(), edmEntitySet)); + final TypedOperand typedOperand = operand.asTypedOperand(); + + if (!(typedOperand.is(EdmBoolean.getInstance()) + && Boolean.TRUE.equals(typedOperand.getTypedValue(Boolean.class)))) { + iter.remove(); + } + } + + } catch (ExpressionVisitException e) { + throw new ODataApplicationException("Exception in filter evaluation", + HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); + } + } + + public static void applyOrderByOption(final OrderByOption orderByOption, final EntitySet entitySet, + final EdmEntitySet edmEntitySet) throws ODataApplicationException { + + if (orderByOption == null) { + return; + } + + try { + applyOrderByOptionInternal(orderByOption, entitySet, edmEntitySet); + } catch (FilterRuntimeException e) { + if (e.getCause() instanceof ODataApplicationException) { + // Throw the nested exception, to send the correct HTTP status code in the HTTP response + throw (ODataApplicationException) e.getCause(); + } else { + throw new ODataApplicationException("Exception in orderBy evaluation", HttpStatusCode.INTERNAL_SERVER_ERROR + .getStatusCode(), Locale.ROOT); + } + } + } + + private static void applyOrderByOptionInternal(final OrderByOption orderByOption, final EntitySet entitySet, + final EdmEntitySet edmEntitySet) throws ODataApplicationException { + Collections.sort(entitySet.getEntities(), new Comparator() { + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public int compare(final Entity e1, final Entity e2) { + // Evaluate the first order option for both entity + // If and only if the result of the previous order option is equals to 0 + // evaluate the next order option until all options are evaluated or they are not equals + int result = 0; + + for (int i = 0; i < orderByOption.getOrders().size() && result == 0; i++) { + try { + final OrderByItem item = orderByOption.getOrders().get(i); + final TypedOperand op1 = + item.getExpression().accept(new ExpressionVisitorImpl(e1, edmEntitySet)).asTypedOperand(); + final TypedOperand op2 = + item.getExpression().accept(new ExpressionVisitorImpl(e2, edmEntitySet)).asTypedOperand(); + + if (op1.isNull() || op2.isNull()) { + if (op1.isNull() && op2.isNull()) { + result = 0; // null is equals to null + } else { + result = op1.isNull() ? -1 : 1; + } + } else { + Object o1 = op1.getValue(); + Object o2 = op2.getValue(); + + if (o1.getClass() == o2.getClass() && o1 instanceof Comparable) { + result = ((Comparable) o1).compareTo(o2); + } else { + result = 0; + } + } + + result = item.isDescending() ? result * -1 : result; + } catch (ODataApplicationException e) { + throw new FilterRuntimeException(e); + } catch (ExpressionVisitException e) { + throw new FilterRuntimeException(e); + } + } + return result; + } + }); + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/TypedOperand.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/TypedOperand.java new file mode 100644 index 000000000..23037b6cb --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/TypedOperand.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression.operand; + +import java.math.BigDecimal; +import java.util.Locale; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.edm.primitivetype.EdmByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDecimal; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDouble; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt16; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt32; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt64; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSingle; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.tecsvc.processor.expression.primitive.EdmNull; + +public class TypedOperand extends VisitorOperand { + + final private EdmType type; + final private EdmProperty edmProperty; + + public TypedOperand(Object value, EdmType type) { + super(value); + this.type = type; + this.edmProperty = null; + } + + public TypedOperand(Object value, EdmType type, EdmProperty edmProperty) { + super(value); + this.type = type; + this.edmProperty = edmProperty; + } + + @Override + public TypedOperand asTypedOperand() throws ODataApplicationException { + if (!isNull() && value.getClass() != getDefaultType((EdmPrimitiveType) type)) { + return asTypedOperand((EdmPrimitiveType) type); + } + return this; + } + + @Override + public TypedOperand asTypedOperand(EdmPrimitiveType... asTypes) throws ODataApplicationException { + if (type.equals(EdmNull.getInstance())) { + return this; + } else if (isNull()) { + return new TypedOperand(null, asTypes[0]); + } + + Object newValue = null; + for (EdmPrimitiveType asType : asTypes) { + // Use BigDecimal for unlimited precision + if (asType.equals(EdmDouble.getInstance()) + || asType.equals(EdmSingle.getInstance()) + || asType.equals(EdmDecimal.getInstance())) { + + try { + newValue = new BigDecimal(value.toString()); + } catch(NumberFormatException e) { + // Nothing to do + } + } else { + // Use type conversion of EdmPrimitive types + try { + final String literal = getLiteral(value); + newValue = tryCast(literal, (EdmPrimitiveType) type); + } catch (EdmPrimitiveTypeException e) { + // Nothing to do + } + } + + if (newValue != null) { + return new TypedOperand(newValue, asType); + } + } + + throw new ODataApplicationException("Cast failed ", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + + public TypedOperand castToCommonType(VisitorOperand otherOperand) throws ODataApplicationException { + final TypedOperand other = otherOperand.asTypedOperand(); + final EdmType oType = other.getType(); + + // Make sure that the EDM type is equals, check also the java type. + // So it is possible, that there is an conversation even if the same + // EdmType is provided. + // For example consider an Edm16 (internal Integer) and Edm16(internal + // Short) + // shortInstance.equals(intInstance) will always be false! + if (type == oType && value != null && other.getValue() != null + && value.getClass() == other.getValue().getClass()) { + return this; + } else if (isNullLiteral() || other.isNullLiteral()) { + return this; + } + + if (type.equals(EdmDouble.getInstance()) || oType.equals(EdmDouble.getInstance())) { + return asTypedOperand(EdmDouble.getInstance()); + } else if (type.equals(EdmSingle.getInstance()) || oType.equals(EdmSingle.getInstance())) { + return asTypedOperand(EdmSingle.getInstance()); + } else if (type.equals(EdmDecimal.getInstance()) || oType.equals(EdmDecimal.getInstance())) { + return asTypedOperand(EdmDecimal.getInstance()); + } else if (type.equals(EdmInt64.getInstance()) || oType.equals(EdmInt64.getInstance())) { + return asTypedOperand(EdmInt64.getInstance()); + } else if (type.equals(EdmInt32.getInstance()) || oType.equals(EdmInt32.getInstance())) { + return asTypedOperand(EdmInt32.getInstance()); + } else if (type.equals(EdmInt16.getInstance()) || oType.equals(equals(EdmInt16.getInstance()))) { + return asTypedOperand(EdmInt16.getInstance()); + } else { + return asTypedOperand((EdmPrimitiveType) type); + } + } + + public EdmType getType() { + return type; + } + + public T getTypedValue(Class clazz) { + return clazz.cast(value); + } + + public boolean isNullLiteral() { + return type.equals(EdmNull.getInstance()); + } + + public boolean isNull() { + return isNullLiteral() || value == null; + } + + public boolean isIntegerType() { + return is(EdmByte.getInstance(), + EdmSByte.getInstance(), + EdmInt16.getInstance(), + EdmInt32.getInstance(), + EdmInt64.getInstance()); + } + + public boolean isDecimalType() { + return is(EdmSingle.getInstance(), + EdmDouble.getInstance(), + EdmDecimal.getInstance()); + } + + public boolean is(EdmPrimitiveType... types) { + if (isNullLiteral()) { + return true; + } + + for (EdmPrimitiveType type : types) { + if (type.equals(this.type)) { + return true; + } + } + + return false; + } + + @Override + public EdmProperty getEdmProperty() { + return edmProperty; + } + + private String getLiteral(Object value) throws EdmPrimitiveTypeException { + final EdmProperty edmProperty = getEdmProperty(); + String uriLiteral = null; + + if (edmProperty != null) { + uriLiteral = ((EdmPrimitiveType) type).valueToString(value, edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode()); + } else { + uriLiteral = ((EdmPrimitiveType) type).valueToString(value, null, null, null, null, null); + } + + return ((EdmPrimitiveType) type).toUriLiteral(uriLiteral); + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/UntypedOperand.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/UntypedOperand.java new file mode 100644 index 000000000..fa82c0993 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/UntypedOperand.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression.operand; + +import java.util.Locale; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.edm.primitivetype.EdmByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDate; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDateTimeOffset; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDecimal; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDouble; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDuration; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt16; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt32; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt64; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSingle; +import org.apache.olingo.commons.core.edm.primitivetype.EdmString; +import org.apache.olingo.commons.core.edm.primitivetype.EdmTime; +import org.apache.olingo.commons.core.edm.primitivetype.EdmTimeOfDay; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.tecsvc.processor.expression.primitive.EdmNull; + +public class UntypedOperand extends VisitorOperand { + + public UntypedOperand(final String literal) { + super(literal); + } + + @Override + public TypedOperand asTypedOperand() throws ODataApplicationException { + return determineType(); + } + + @Override + public TypedOperand asTypedOperand(final EdmPrimitiveType... types) throws ODataApplicationException { + final String literal = (String) value; + Object newValue = null; + + // First try the null literal + if ((newValue = tryCast(literal, EdmNull.getInstance())) != null) { + return new TypedOperand(newValue, EdmNull.getInstance()); + } + + // Than try the given types + for (EdmPrimitiveType type : types) { + newValue = tryCast(literal, type); + + if (newValue != null) { + return new TypedOperand(newValue, type); + } + } + + throw new ODataApplicationException("Cast failed", HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), + Locale.ROOT); + } + + public TypedOperand determineType() throws ODataApplicationException { + final String literal = (String) value; + Object newValue = null; + + // Null literal + if ((newValue = tryCast(literal, EdmNull.getInstance())) != null) { + return new TypedOperand(newValue, EdmNull.getInstance()); + } + + // String + if ((newValue = tryCast(literal, EdmString.getInstance())) != null) { + return new TypedOperand(newValue, EdmString.getInstance()); + } + + // Date + if ((newValue = tryCast(literal, EdmDateTimeOffset.getInstance())) != null) { + return new TypedOperand(newValue, EdmDateTimeOffset.getInstance()); + } + + if ((newValue = tryCast(literal, EdmDate.getInstance())) != null) { + return new TypedOperand(newValue, EdmDate.getInstance()); + } + + if ((newValue = tryCast(literal, EdmTimeOfDay.getInstance())) != null) { + return new TypedOperand(newValue, EdmTimeOfDay.getInstance()); + } + + if ((newValue = tryCast(literal, EdmTime.getInstance())) != null) { + return new TypedOperand(newValue, EdmTime.getInstance()); + } + + if ((newValue = tryCast(literal, EdmDuration.getInstance())) != null) { + return new TypedOperand(newValue, EdmDuration.getInstance()); + } + + // Integer + if ((newValue = tryCast(literal, EdmSByte.getInstance())) != null) { + return new TypedOperand(newValue, EdmSByte.getInstance()); + } + + if ((newValue = tryCast(literal, EdmByte.getInstance())) != null) { + return new TypedOperand(newValue, EdmByte.getInstance()); + } + + if ((newValue = tryCast(literal, EdmInt16.getInstance())) != null) { + return new TypedOperand(newValue, EdmInt16.getInstance()); + } + + if ((newValue = tryCast(literal, EdmInt32.getInstance())) != null) { + return new TypedOperand(newValue, EdmInt32.getInstance()); + } + + if ((newValue = tryCast(literal, EdmInt64.getInstance())) != null) { + return new TypedOperand(newValue, EdmInt64.getInstance()); + } + + // Decimal + if ((newValue = tryCast(literal, EdmDecimal.getInstance())) != null) { + return new TypedOperand(newValue, EdmDecimal.getInstance()); + } + + // Float + if ((newValue = tryCast(literal, EdmSingle.getInstance())) != null) { + return new TypedOperand(newValue, EdmSingle.getInstance()); + } + + if ((newValue = tryCast(literal, EdmDouble.getInstance())) != null) { + return new TypedOperand(newValue, EdmDouble.getInstance()); + } + + throw new ODataApplicationException("Could not determine type for literal " + literal, + HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); + } + + @Override + public EdmProperty getEdmProperty() { + return null; + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/VisitorOperand.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/VisitorOperand.java new file mode 100644 index 000000000..02f6fb065 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operand/VisitorOperand.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression.operand; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.core.edm.primitivetype.EdmByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDecimal; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDouble; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt16; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt32; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt64; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSingle; +import org.apache.olingo.server.api.ODataApplicationException; + +public abstract class VisitorOperand { + final static private HashMap> defaultTypeMapping = new HashMap>(); + protected Object value; + + static { + defaultTypeMapping.put(EdmByte.getInstance(), BigInteger.class); + defaultTypeMapping.put(EdmSByte.getInstance(), BigInteger.class); + defaultTypeMapping.put(EdmInt16.getInstance(), BigInteger.class); + defaultTypeMapping.put(EdmInt32.getInstance(), BigInteger.class); + defaultTypeMapping.put(EdmInt64.getInstance(), BigInteger.class); + + defaultTypeMapping.put(EdmSingle.getInstance(), BigDecimal.class); + defaultTypeMapping.put(EdmDouble.getInstance(), BigDecimal.class); + defaultTypeMapping.put(EdmDecimal.getInstance(), BigDecimal.class); + } + + public VisitorOperand(Object value) { + this.value = value; + } + + public abstract TypedOperand asTypedOperand() throws ODataApplicationException; + + public abstract TypedOperand asTypedOperand(EdmPrimitiveType... types) throws ODataApplicationException; + + public abstract EdmProperty getEdmProperty(); + + public Object getValue() { + return value; + } + + protected Object castTo(final String value, EdmPrimitiveType type) throws EdmPrimitiveTypeException { + final EdmProperty edmProperty = getEdmProperty(); + + if (edmProperty != null) { + return type.valueOfString(value, edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), + edmProperty.isUnicode(), getDefaultType(type)); + } else { + return type.valueOfString(value, null, null, null, null, null, getDefaultType(type)); + } + } + + protected Class getDefaultType(EdmPrimitiveType type) { + return defaultTypeMapping.get(type) != null ? defaultTypeMapping.get(type) : type.getDefaultType(); + } + + protected Object tryCast(final String literal, final EdmPrimitiveType type) { + try { + return castTo(type.fromUriLiteral(literal), type); + } catch (EdmPrimitiveTypeException e) { + return null; + } + } + +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/BinaryOperator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/BinaryOperator.java new file mode 100644 index 000000000..f41e79801 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/BinaryOperator.java @@ -0,0 +1,318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression.operation; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Locale; + +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.edm.primitivetype.EdmBoolean; +import org.apache.olingo.commons.core.edm.primitivetype.EdmByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDate; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDateTimeOffset; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDouble; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDuration; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt16; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt32; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt64; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSByte; +import org.apache.olingo.commons.core.edm.primitivetype.EdmSingle; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; +import org.apache.olingo.server.tecsvc.processor.expression.operand.TypedOperand; +import org.apache.olingo.server.tecsvc.processor.expression.operand.VisitorOperand; +import org.apache.olingo.server.tecsvc.processor.expression.primitive.EdmNull; + +public class BinaryOperator { + private static final int FACTOR_SECOND_INT = 1000; + private static final BigDecimal FACTOR_SECOND = new BigDecimal(1000); + private static final BigInteger EDM_SBYTE_MIN = BigInteger.valueOf(Byte.MIN_VALUE); + private static final BigInteger EDN_SBYTE_MAX = BigInteger.valueOf(Byte.MAX_VALUE); + private static final BigInteger EDM_BYTE_MIN = BigInteger.ZERO; + private static final BigInteger EDM_BYTE_MAX = BigInteger.valueOf(((Byte.MAX_VALUE * 2) + 1)); + private static final BigInteger EDM_INT16_MIN = BigInteger.valueOf(Short.MIN_VALUE); + private static final BigInteger EDM_INT16_MAX = BigInteger.valueOf(Short.MAX_VALUE); + private static final BigInteger EDM_INT32_MIN = BigInteger.valueOf(Integer.MIN_VALUE); + private static final BigInteger EDM_INT32_MAX = BigInteger.valueOf(Integer.MAX_VALUE); + private static final BigInteger EDM_INT64_MIN = BigInteger.valueOf(Long.MIN_VALUE); + private static final BigInteger EDM_INT64_MAX = BigInteger.valueOf(Long.MAX_VALUE); + private static final BigDecimal EDM_SINGLE_MIN = BigDecimal.valueOf(Float.MIN_VALUE); + private static final BigDecimal EDM_SINGLE_MAX = BigDecimal.valueOf(Float.MAX_VALUE); + + private static final int EQUALS = 0; + private static final int LESS_THAN = -1; + private static final int GREATER_THAN = 1; + + private TypedOperand right; + private TypedOperand left; + + public BinaryOperator(final VisitorOperand leftOperand, final VisitorOperand rightOperand) + throws ODataApplicationException { + left = leftOperand.asTypedOperand(); + right = rightOperand.asTypedOperand(); + + left = left.castToCommonType(right); + right = right.castToCommonType(left); + } + + public VisitorOperand andOperator() throws ODataApplicationException { + Boolean result = null; + if (left.is(EdmBoolean.getInstance()) && right.is(EdmBoolean.getInstance())) { + if (Boolean.TRUE.equals(left.getValue()) && Boolean.TRUE.equals(right.getValue())) { + result = true; + } else if (Boolean.FALSE.equals(left.getValue()) || Boolean.FALSE.equals(right.getValue())) { + result = false; + } + + return new TypedOperand(result, EdmBoolean.getInstance()); + } else { + throw new ODataApplicationException("Add operator needs two binary operands", + HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + public VisitorOperand orOperator() throws ODataApplicationException { + Boolean result = null; + if (left.is(EdmBoolean.getInstance()) && right.is(EdmBoolean.getInstance())) { + if (Boolean.TRUE.equals(left.getValue()) || Boolean.TRUE.equals(right.getValue())) { + result = true; + } else if (Boolean.FALSE.equals(left.getValue()) && Boolean.FALSE.equals(right.getValue())) { + result = false; + } + + return new TypedOperand(result, EdmBoolean.getInstance()); + } else { + throw new ODataApplicationException("Or operator needs two binary operands", + HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + public VisitorOperand equalsOperator() { + final boolean result = isBinaryComparisonNecessary() && binaryComparison(EQUALS); + return new TypedOperand(result, EdmBoolean.getInstance()); + } + + public VisitorOperand notEqualsOperator() { + final VisitorOperand equalsOperator = equalsOperator(); + return new TypedOperand(!(Boolean) equalsOperator.getValue(), EdmBoolean.getInstance()); + } + + private boolean isBinaryComparisonNecessary() { + // binaryComparison() need to be called, if both operand are either null or not null + return !(left.isNull() ^ right.isNull()); + } + + public VisitorOperand greaterEqualsOperator() { + final boolean result = isBinaryComparisonNecessary() && binaryComparison(GREATER_THAN, EQUALS); + return new TypedOperand(result, EdmBoolean.getInstance()); + } + + public VisitorOperand greaterThanOperator() { + final boolean result = isBinaryComparisonNecessary() && binaryComparison(GREATER_THAN); + return new TypedOperand(result, EdmBoolean.getInstance()); + } + + public VisitorOperand lessEqualsOperator() { + final boolean result = isBinaryComparisonNecessary() && binaryComparison(LESS_THAN, EQUALS); + return new TypedOperand(result, EdmBoolean.getInstance()); + } + + public VisitorOperand lessThanOperator() { + final boolean result = isBinaryComparisonNecessary() && binaryComparison(LESS_THAN); + return new TypedOperand(result, EdmBoolean.getInstance()); + } + + private boolean binaryComparison(int... expect) { + int result; + + if (left.isNull() && right.isNull()) { + result = 0; // null is equals to null + } else { + // left and right are not null! + if (left.isIntegerType()) { + result = left.getTypedValue(BigInteger.class).compareTo(right.getTypedValue(BigInteger.class)); + } else if (left.isDecimalType()) { + result = left.getTypedValue(BigDecimal.class).compareTo(right.getTypedValue(BigDecimal.class)); + } else { + result = left.getValue().equals(right.getValue()) ? 0 : 1; + } + } + for (int expectedValue : expect) { + if (expectedValue == result) { + return true; + } + } + + return false; + } + + public VisitorOperand arithmeticOperator(BinaryOperatorKind operator) throws ODataApplicationException { + if (left.isNull() || right.isNull()) { + return new TypedOperand(new Object(), EdmNull.getInstance()); + } else { + if (left.isIntegerType()) { + final BigInteger result = integerArithmeticOperation(operator); + return new TypedOperand(result, determineResultType(result, left)); + } else if (left.isDecimalType()) { + final BigDecimal result = decimalArithmeticOperation(operator); + return new TypedOperand(result, determineResultType(result, left)); + } else if (left.is(EdmDate.getInstance(), EdmDuration.getInstance(), EdmDateTimeOffset.getInstance())) { + return dateArithmeticOperation(operator); + } else { + throw new ODataApplicationException("Invalid type", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT); + } + } + } + + private EdmType determineResultType(final Number arithmeticResult, TypedOperand leftOperand) { + // Left and right operand have the same typed, so it is enough to check the type of the left operand + if (leftOperand.isDecimalType()) { + final BigDecimal value = (BigDecimal) arithmeticResult; + if (value.compareTo(EDM_SINGLE_MIN) >= 0 && value.compareTo(EDM_SINGLE_MAX) <= 0) { + return EdmSingle.getInstance(); + } else { + return EdmDouble.getInstance(); + } + } else { + final BigInteger value = (BigInteger) arithmeticResult; + + if (value.compareTo(EDN_SBYTE_MAX) <= 0 && value.compareTo(EDM_SBYTE_MIN) >= 0) { + return EdmSByte.getInstance(); + } + if (value.compareTo(EDM_BYTE_MAX) <= 0 && value.compareTo(EDM_BYTE_MIN) >= 0) { + return EdmByte.getInstance(); + } + if (value.compareTo(EDM_INT16_MAX) <= 0 && value.compareTo(EDM_INT16_MIN) >= 0) { + return EdmInt16.getInstance(); + } + if (value.compareTo(EDM_INT32_MAX) <= 0 && value.compareTo(EDM_INT32_MIN) >= 0) { + return EdmInt32.getInstance(); + } + if (value.compareTo(EDM_INT64_MAX) <= 0 && value.compareTo(EDM_INT64_MIN) >= 0) { + return EdmInt64.getInstance(); + } + // Choose double instead single because precision is higher (52 bits instead of 23) + return EdmDouble.getInstance(); + } + } + + private VisitorOperand dateArithmeticOperation(BinaryOperatorKind operator) throws ODataApplicationException { + VisitorOperand result = null; + + if (left.is(EdmDate.getInstance())) { + if (right.is(EdmDate.getInstance()) && operator == BinaryOperatorKind.SUB) { + long millis = left.getTypedValue(Calendar.class).getTimeInMillis() + - left.getTypedValue(Calendar.class).getTimeInMillis(); + + result = new TypedOperand(new BigDecimal(millis).divide(FACTOR_SECOND), EdmDuration.getInstance()); + } else if (right.is(EdmDuration.getInstance()) && operator == BinaryOperatorKind.ADD) { + long millis = left.getTypedValue(Calendar.class).getTimeInMillis() + + (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT); + + result = new TypedOperand(new Timestamp(millis), EdmDateTimeOffset.getInstance()); + } else if (right.is(EdmDuration.getInstance()) && operator == BinaryOperatorKind.SUB) { + long millis = left.getTypedValue(Calendar.class).getTimeInMillis() + - (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT); + + result = new TypedOperand(new Timestamp(millis), EdmDateTimeOffset.getInstance()); + } + } else if (left.is(EdmDuration.getInstance())) { + if (right.is(EdmDuration.getInstance()) && operator == BinaryOperatorKind.ADD) { + long seconds = left.getTypedValue(BigDecimal.class).longValue() + + right.getTypedValue(BigDecimal.class).longValue(); + + result = new TypedOperand(new BigDecimal(seconds), EdmDuration.getInstance()); + } else if (right.is(EdmDuration.getInstance()) && operator == BinaryOperatorKind.SUB) { + long seconds = left.getTypedValue(BigDecimal.class).longValue() + - right.getTypedValue(BigDecimal.class).longValue(); + + result = new TypedOperand(new BigDecimal(seconds), EdmDuration.getInstance()); + } + } else if (left.is(EdmDateTimeOffset.getInstance())) { + if (right.is(EdmDuration.getInstance()) && operator == BinaryOperatorKind.ADD) { + long millis = left.getTypedValue(Timestamp.class).getTime() + + (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT); + + result = new TypedOperand(new Timestamp(millis), EdmDateTimeOffset.getInstance()); + } else if (right.is(EdmDuration.getInstance()) && operator == BinaryOperatorKind.SUB) { + long millis = left.getTypedValue(Timestamp.class).getTime() + - (right.getTypedValue(BigDecimal.class).longValue() * FACTOR_SECOND_INT); + + result = new TypedOperand(new Timestamp(millis), EdmDateTimeOffset.getInstance()); + } else if (right.is(EdmDateTimeOffset.getInstance()) && operator == BinaryOperatorKind.SUB) { + long millis = left.getTypedValue(Timestamp.class).getTime() + - right.getTypedValue(Timestamp.class).getTime(); + + result = new TypedOperand(new BigDecimal(millis).divide(FACTOR_SECOND), EdmDuration.getInstance()); + } + } + + if (result == null) { + throw new ODataApplicationException("Invalid operation / operand", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT); + } else { + return result; + } + } + + private BigDecimal decimalArithmeticOperation(BinaryOperatorKind operator) throws ODataApplicationException { + final BigDecimal left = this.left.getTypedValue(BigDecimal.class); + final BigDecimal right = this.right.getTypedValue(BigDecimal.class); + + switch (operator) { + case ADD: + return left.add(right); + case DIV: + return left.divide(left); + case MUL: + return left.multiply(right); + case SUB: + return left.subtract(right); + default: + throw new ODataApplicationException("Operator not valid", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT); + } + } + + private BigInteger integerArithmeticOperation(BinaryOperatorKind operator) throws ODataApplicationException { + final BigInteger left = this.left.getTypedValue(BigInteger.class); + final BigInteger right = this.right.getTypedValue(BigInteger.class); + + switch (operator) { + case ADD: + return left.add(right); + case DIV: + return left.divide(right); + case MUL: + return left.multiply(right); + case SUB: + return left.subtract(right); + case MOD: + return left.mod(right); + default: + throw new ODataApplicationException("Operator not valid", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT); + } + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/MethodCallOperator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/MethodCallOperator.java new file mode 100644 index 000000000..ece4df4ff --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/MethodCallOperator.java @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression.operation; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.edm.primitivetype.EdmBoolean; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDate; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDateTimeOffset; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDecimal; +import org.apache.olingo.commons.core.edm.primitivetype.EdmInt32; +import org.apache.olingo.commons.core.edm.primitivetype.EdmString; +import org.apache.olingo.commons.core.edm.primitivetype.EdmTimeOfDay; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.tecsvc.processor.expression.operand.TypedOperand; +import org.apache.olingo.server.tecsvc.processor.expression.operand.VisitorOperand; + +public class MethodCallOperator { + + final private List parameters; + + public MethodCallOperator(List parameters) { + this.parameters = parameters; + } + + public VisitorOperand endsWith() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).endsWith(params.get(1)); + } + }, EdmBoolean.getInstance()); + } + + public VisitorOperand indexOf() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).indexOf(params.get(1)); + } + }, EdmInt32.getInstance()); + } + + public VisitorOperand startsWith() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).startsWith(params.get(1)); + } + }, EdmBoolean.getInstance()); + } + + public VisitorOperand toLower() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).toLowerCase(); + } + }, EdmString.getInstance()); + } + + public VisitorOperand toUpper() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).toUpperCase(); + } + }, EdmString.getInstance()); + } + + public VisitorOperand trim() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).trim(); + } + }, EdmString.getInstance()); + } + + public VisitorOperand substring() throws ODataApplicationException { + final TypedOperand valueOperand = parameters.get(0).asTypedOperand(); + final TypedOperand startOperand = parameters.get(1).asTypedOperand(); + + if (valueOperand.isNull() || startOperand.isNull()) { + return new TypedOperand(null, EdmString.getInstance()); + } else if (valueOperand.is(EdmString.getInstance()) && startOperand.isIntegerType()) { + final String value = valueOperand.getTypedValue(String.class); + final BigInteger start = startOperand.getTypedValue(BigInteger.class); + int end = value.length(); + + if (parameters.size() == 3) { + final TypedOperand lengthOperand = parameters.get(2).asTypedOperand(); + + if (lengthOperand.isNull()) { + return new TypedOperand(null, EdmString.getInstance()); + } else if (lengthOperand.isIntegerType()) { + end = Math.min(start.add(lengthOperand.getTypedValue(BigInteger.class)).intValue(), value.length()); + } else { + throw new ODataApplicationException("Third substring parameter should be Edm.Int32", + HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + return new TypedOperand(value.substring(Math.min(start.intValue(), value.length()), end), + EdmString.getInstance()); + } else { + throw new ODataApplicationException("Substring has invalid parameters. First parameter should be Edm.String," + + " second parameter should be Edm.Int32", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + public VisitorOperand contains() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).contains(params.get(1)); + } + }, EdmBoolean.getInstance()); + } + + public VisitorOperand concat() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0) + params.get(1); + } + }, EdmString.getInstance()); + } + + public VisitorOperand length() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(List params) { + return params.get(0).length(); + } + }, EdmInt32.getInstance()); + } + + public VisitorOperand year() throws ODataApplicationException { + return dateFunction(new DateFunction() { + @Override + public Object perform(Calendar calendar, TypedOperand operand) { + return calendar.get(Calendar.YEAR); + } + }, EdmInt32.getInstance(), EdmDateTimeOffset.getInstance(), EdmDate.getInstance()); + } + + public VisitorOperand month() throws ODataApplicationException { + return dateFunction(new DateFunction() { + @Override + public Object perform(Calendar calendar, TypedOperand operand) { + // Month is 0-based! + return calendar.get(Calendar.MONTH) + 1; + } + }, EdmInt32.getInstance(), EdmDateTimeOffset.getInstance(), EdmDate.getInstance()); + } + + public VisitorOperand day() throws ODataApplicationException { + return dateFunction(new DateFunction() { + @Override + public Object perform(Calendar calendar, TypedOperand operand) { + return calendar.get(Calendar.DAY_OF_MONTH); + } + }, EdmInt32.getInstance(), EdmDateTimeOffset.getInstance(), EdmDate.getInstance()); + } + + public VisitorOperand hour() throws ODataApplicationException { + return dateFunction(new DateFunction() { + @Override + public Object perform(Calendar calendar, TypedOperand operand) { + return calendar.get(Calendar.HOUR_OF_DAY); + } + }, EdmInt32.getInstance(), EdmDateTimeOffset.getInstance(), EdmTimeOfDay.getInstance()); + } + + public VisitorOperand minute() throws ODataApplicationException { + return dateFunction(new DateFunction() { + @Override + public Object perform(Calendar calendar, TypedOperand operand) { + return calendar.get(Calendar.MINUTE); + } + }, EdmInt32.getInstance(), EdmDateTimeOffset.getInstance(), EdmTimeOfDay.getInstance()); + } + + public VisitorOperand second() throws ODataApplicationException { + return dateFunction(new DateFunction() { + @Override + public Object perform(Calendar calendar, TypedOperand operand) { + return calendar.get(Calendar.SECOND); + } + }, EdmInt32.getInstance(), EdmDateTimeOffset.getInstance(), EdmTimeOfDay.getInstance()); + } + + public VisitorOperand fractionalseconds() throws ODataApplicationException { + return dateFunction(new DateFunction() { + @Override + public Object perform(Calendar calendar, TypedOperand operand) { + if (operand.getValue() instanceof Timestamp) { + return new BigDecimal(operand.getTypedValue(Timestamp.class).getNanos()).divide(BigDecimal + .valueOf(1000 * 1000 * 1000)); + } else { + return new BigDecimal(calendar.get(Calendar.MILLISECOND)).divide(BigDecimal.valueOf(1000)); + } + } + }, EdmDecimal.getInstance(), EdmDateTimeOffset.getInstance(), EdmTimeOfDay.getInstance()); + } + + public VisitorOperand round() throws ODataApplicationException { + final TypedOperand operand = parameters.get(0).asTypedOperand(); + if (operand.isNull()) { + return operand; + } else if (operand.isDecimalType()) { + return new TypedOperand(operand.getTypedValue(BigDecimal.class).round(new MathContext(1, RoundingMode.HALF_UP)), + operand.getType()); + } else { + throw new ODataApplicationException("Invalid type", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + public VisitorOperand floor() throws ODataApplicationException { + final TypedOperand operand = parameters.get(0).asTypedOperand(); + if (operand.isNull()) { + return operand; + } else if (operand.isDecimalType()) { + return new TypedOperand(operand.getTypedValue(BigDecimal.class).round(new MathContext(1, RoundingMode.FLOOR)), + operand.getType()); + } else { + throw new ODataApplicationException("Invalid type", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + public VisitorOperand ceiling() throws ODataApplicationException { + final TypedOperand operand = parameters.get(0).asTypedOperand(); + if (operand.isNull()) { + return operand; + } else if (operand.isDecimalType()) { + return new TypedOperand(operand.getTypedValue(BigDecimal.class).round(new MathContext(1, RoundingMode.CEILING)), + operand.getType()); + } else { + throw new ODataApplicationException("Invalid type", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + private interface StringFunction { + Object perform(List params); + } + + private interface DateFunction { + Object perform(Calendar calendar, TypedOperand operand); + } + + private VisitorOperand dateFunction(DateFunction f, EdmType returnType, EdmPrimitiveType... expectedTypes) + throws ODataApplicationException { + final TypedOperand operand = parameters.get(0).asTypedOperand(); + + if (operand.is(expectedTypes)) { + if (!operand.isNull()) { + Calendar calendar = null; + if (operand.is(EdmDate.getInstance())) { + calendar = operand.getTypedValue(Calendar.class); + } else if (operand.is(EdmDateTimeOffset.getInstance())) { + final Timestamp timestamp = operand.getTypedValue(Timestamp.class); + calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + calendar.setTimeInMillis(timestamp.getTime()); + } else if (operand.is(EdmTimeOfDay.getInstance())) { + calendar = operand.getTypedValue(Calendar.class); + } else { + throw new ODataApplicationException("Invalid type", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + + return new TypedOperand(f.perform(calendar, operand), returnType); + } else { + return new TypedOperand(null, returnType); + } + } else { + throw new ODataApplicationException("Invalid type", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + } + + private VisitorOperand stringFunction(StringFunction f, EdmType returnValue) throws ODataApplicationException { + List stringParameters = getParametersAsString(); + if (stringParameters.contains(null)) { + return new TypedOperand(null, returnValue); + } else { + return new TypedOperand(f.perform(stringParameters), returnValue); + } + } + + private List getParametersAsString() throws ODataApplicationException { + List result = new ArrayList(); + + for (VisitorOperand param : parameters) { + TypedOperand operand = param.asTypedOperand(); + if (operand.isNull()) { + result.add(null); + } else if (operand.is(EdmString.getInstance())) { + result.add(operand.getTypedValue(String.class)); + } else { + throw new ODataApplicationException("Invalid parameter. Expected Edm.String", HttpStatusCode.BAD_REQUEST + .getStatusCode(), Locale.ROOT); + } + } + + return result; + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/UnaryOperator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/UnaryOperator.java new file mode 100644 index 000000000..b0b5435ac --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/operation/UnaryOperator.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression.operation; + + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Locale; + +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.edm.primitivetype.EdmBoolean; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDuration; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.tecsvc.processor.expression.operand.TypedOperand; +import org.apache.olingo.server.tecsvc.processor.expression.operand.VisitorOperand; + +public class UnaryOperator { + final private TypedOperand operand; + + public UnaryOperator(VisitorOperand operand) throws ODataApplicationException { + this.operand = operand.asTypedOperand(); + } + + public VisitorOperand minusOperation() throws ODataApplicationException { + if (operand.isNull()) { + return operand; + } else if (operand.isIntegerType()) { + return new TypedOperand(operand.getTypedValue(BigInteger.class).negate(), operand.getType()); + } else if (operand.isDecimalType() || operand.is(EdmDuration.getInstance())) { + return new TypedOperand(operand.getTypedValue(BigDecimal.class).negate(), operand.getType()); + } else { + throw new ODataApplicationException("Unsupported type", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT); + } + } + + public VisitorOperand notOperation() throws ODataApplicationException { + if (operand.isNull()) { + return operand; + } else if (operand.is(EdmBoolean.getInstance())) { + return new TypedOperand(!operand.getTypedValue(Boolean.class), operand.getType()); + } else { + throw new ODataApplicationException("Unsupported type", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT); + } + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/primitive/EdmNull.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/primitive/EdmNull.java new file mode 100644 index 000000000..02b624d70 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/expression/primitive/EdmNull.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.processor.expression.primitive; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.core.edm.primitivetype.SingletonPrimitiveType; + + +public final class EdmNull extends SingletonPrimitiveType { + + private static final EdmNull instance = new EdmNull(); + + public static EdmNull getInstance() { + return instance; + } + + @Override + public Class getDefaultType() { + return Object.class; + } + + @Override + protected T internalValueOfString(String value, Boolean isNullable, Integer maxLength, Integer precision, + Integer scale, Boolean isUnicode, Class returnType) throws EdmPrimitiveTypeException { + if (!value.equals("null")) { + throw new EdmPrimitiveTypeException("The literal '" + value + "' has illegal content."); + } + + if (returnType.isAssignableFrom(Object.class)) { + return returnType.cast(new Object()); + } else { + throw new ClassCastException("unsupported return type " + returnType.getSimpleName()); + } + } + + @Override + protected String internalValueToString(T value, Boolean isNullable, Integer maxLength, Integer precision, + Integer scale, Boolean isUnicode) throws EdmPrimitiveTypeException { + return "null"; + } + +}