From 9244d682f76596912ba2e25e87dd0aab613b302a Mon Sep 17 00:00:00 2001 From: ramya vasanth Date: Tue, 14 May 2019 14:51:23 +0530 Subject: [PATCH] [OLINGO-1358] Substringof Method support --- .../client/FilterSystemQueryITCase.java | 19 +- .../fit/tecsvc/http/BasicHttpITCase.java | 22 + .../queryoption/expression/MethodKind.java | 3 +- .../core/uri/parser/ExpressionParser.java | 4 +- .../server/core/uri/parser/UriTokenizer.java | 4 + .../queryoption/expression/MethodImpl.java | 1 + .../core/uri/parser/ExpressionParserTest.java | 402 ++++++++++++++++++ .../expression/ExpressionVisitorImpl.java | 2 + .../operation/MethodCallOperator.java | 9 + .../core/uri/parser/ExpressionParserTest.java | 41 ++ .../core/uri/testutil/FilterValidator.java | 25 ++ 11 files changed, 529 insertions(+), 3 deletions(-) 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 index 17308d354..017cabaab 100644 --- 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 @@ -1071,7 +1071,7 @@ public class FilterSystemQueryITCase extends AbstractParamTecSvcITCase { } return response; } - + private void fail(final String entitySet, final String filterString, final HttpStatusCode errorCode) { try { sendRequest(entitySet, filterString); @@ -1080,4 +1080,21 @@ public class FilterSystemQueryITCase extends AbstractParamTecSvcITCase { assertEquals(errorCode.getStatusCode(), e.getStatusLine().getStatusCode()); } } + + @Test + public void substringOf() { + ODataRetrieveResponse response = + sendRequest(ES_ALL_PRIM, "substringof('Second',PropertyString)"); + assertEquals(1, response.getBody().getEntities().size()); + assertEquals("Second Resource - negative values", + response.getBody().getEntities().get(0).getProperty("PropertyString") + .getPrimitiveValue().toValue()); + } + + @Test + public void substringOfWithNegativeValues() { + fail(ES_ALL_PRIM, "substringof(123,PropertyString)", HttpStatusCode.BAD_REQUEST); + fail(ES_ALL_PRIM, "substringof(PropertyString,123)", HttpStatusCode.BAD_REQUEST); + fail(ES_ALL_PRIM, "substringof(123,123)", HttpStatusCode.BAD_REQUEST); + } } diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java index 7018e360c..24f3e28ea 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java @@ -405,4 +405,26 @@ public class BasicHttpITCase extends AbstractBaseTestITCase { assertEquals("3", IOUtils.toString(connection.getInputStream())); connection.disconnect(); } + + @Test + public void testSubstringOfWithParameterAlias() throws Exception { + URL url = new URL(SERVICE_URI + "ESAllPrim?$filter=substringof(@word,PropertyString)&@word='Second'"); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod(HttpMethod.GET.name()); + connection.setRequestProperty(HttpHeader.ACCEPT, "application/json"); + connection.connect(); + + assertEquals(HttpStatusCode.OK.getStatusCode(), connection.getResponseCode()); + String content = IOUtils.toString(connection.getInputStream()); + assertNotNull(content); + assertTrue(content.contains("\"value\":[{\"PropertyInt16\":-32768," + + "\"PropertyString\":\"Second Resource - negative values\"," + + "\"PropertyBoolean\":false,\"PropertyByte\":0,\"PropertySByte\":-128,\"PropertyInt32\":-2147483648," + + "\"PropertyInt64\":-9223372036854775808,\"PropertySingle\":-1.79E8,\"PropertyDouble\":-179000.0," + + "\"PropertyDecimal\":-34,\"PropertyBinary\":\"ASNFZ4mrze8=\",\"PropertyDate\":\"2015-11-05\"," + + "\"PropertyDateTimeOffset\":\"2005-12-03T07:17:08Z\",\"PropertyDuration\":\"PT9S\"," + + "\"PropertyGuid\":\"76543201-23ab-cdef-0123-456789dddfff\",\"PropertyTimeOfDay\":\"23:49:14\"}]}")); + connection.disconnect(); + } } diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java index 643332569..527fddc8a 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/expression/MethodKind.java @@ -52,7 +52,8 @@ public enum MethodKind { GEOLENGTH("geo.length"), GEOINTERSECTS("geo.intersects"), CAST("cast"), - ISOF("isof"); + ISOF("isof"), + SUBSTRINGOF("substringof"); private String syntax; diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java index ed0af1c94..6be186d88 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java @@ -153,6 +153,7 @@ public class ExpressionParser { temp.put(TokenKind.ToupperMethod, MethodKind.TOUPPER); temp.put(TokenKind.TrimMethod, MethodKind.TRIM); temp.put(TokenKind.YearMethod, MethodKind.YEAR); + temp.put(TokenKind.SubstringofMethod, MethodKind.SUBSTRINGOF); tokenToMethod = Collections.unmodifiableMap(temp); } @@ -386,7 +387,7 @@ public class ExpressionParser { if (tokenizer.next(TokenKind.ODataIdentifier)) { return parseFirstMemberExpr(TokenKind.ODataIdentifier); } - + throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX); } @@ -527,6 +528,7 @@ public class ExpressionParser { case STARTSWITH: case INDEXOF: case CONCAT: + case SUBSTRINGOF: ParserHelper.bws(tokenizer); final Expression stringParameter1 = parseExpression(); checkType(stringParameter1, EdmPrimitiveTypeKind.String); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java index 236325411..50c906020 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java @@ -170,6 +170,7 @@ public class UriTokenizer { ToupperMethod, TrimMethod, YearMethod, + SubstringofMethod, IsDefinedMethod, // for the aggregation extension @@ -638,6 +639,9 @@ public class UriTokenizer { case YearMethod: found = nextMethod("year"); break; + case SubstringofMethod: + found = nextMethod("substringof"); + break; // Method for the aggregation extension case IsDefinedMethod: diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java index 126a6b22c..b9f8542f8 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java @@ -54,6 +54,7 @@ public class MethodImpl implements Method { case CONTAINS: case STARTSWITH: case ENDSWITH: + case SUBSTRINGOF: kind = EdmPrimitiveTypeKind.Boolean; break; case LENGTH: diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java index 5646a4e98..92c09da41 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java @@ -25,14 +25,30 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntityContainer; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; +import org.apache.olingo.server.core.uri.queryoption.AliasQueryOptionImpl; import org.apache.olingo.server.core.uri.validator.UriValidationException; import org.junit.Test; +import org.mockito.Mockito; public class ExpressionParserTest { @@ -51,8 +67,27 @@ public class ExpressionParserTest { assertEquals("{null EQ null}", parseExpression("null eq null").toString()); wrongExpression("5 eq '5'"); + } + @Test + public void testIntegerTypes() throws Exception { + Expression expression = parseExpression("5 ne 545678979"); + assertEquals("{5 NE 545678979}", expression.toString()); + + expression = parseExpression("5456 eq 5456"); + assertEquals("{5456 EQ 5456}", expression.toString()); + + expression = parseExpression("null ne 54561234567"); + assertEquals("{null NE 54561234567}", expression.toString()); + + expression = parseExpression("null ne 255"); + assertEquals("{null NE 255}", expression.toString()); + + expression = parseExpression("123 le 2551234567890000999999"); + assertEquals("{123 LE 2551234567890000999999}", expression.toString()); + } + @Test public void relational() throws Exception { Expression expression = parseExpression("5 gt 5"); @@ -217,6 +252,12 @@ public class ExpressionParserTest { wrongExpression("endswith('a',1)"); assertEquals("{concat ['a', 'b']}", parseExpression("concat( 'a' ,\t'b' )").toString()); + + parseMethod(TokenKind.SubstringofMethod, "'a'", "'b'"); + parseMethod(TokenKind.SubstringofMethod, "' '", "'b'"); + parseMethod(TokenKind.SubstringofMethod, "' '", "' '"); + parseMethod(TokenKind.SubstringofMethod, null, "'a'"); + wrongExpression("substringof('a',1)"); } @Test @@ -239,6 +280,62 @@ public class ExpressionParserTest { assertEquals("{cast [42, Edm.SByte]}", parseExpression("cast( 42\t, Edm.SByte )").toString()); } + @Test + public void twoParameterAliasMethods() throws Exception { + parseMethodWithParametersWithAlias(TokenKind.SubstringofMethod, "'a'", "'b'"); + parseMethodWithParametersWithoutAlias(TokenKind.SubstringofMethod, "'a'", "'b'"); + } + + private void parseMethodWithParametersWithoutAlias(TokenKind kind, String... parameters) + throws UriParserException, UriValidationException { + final String methodName = kind.name().substring(0, kind.name().indexOf("Method")).toLowerCase(Locale.ROOT) + .replace("geo", "geo."); + String expressionString = methodName + '('; + expressionString += "@word1"; + expressionString += ','; + expressionString += parameters[1]; + expressionString += ')'; + expressionString += "&@word1=" + parameters[0]; + + Map alias = new HashMap(); + AliasQueryOptionImpl aliasQueryOption = new AliasQueryOptionImpl(); + aliasQueryOption.setName("@word"); + aliasQueryOption.setText("\'a\'"); + alias.put("@word", aliasQueryOption); + UriTokenizer tokenizer = new UriTokenizer(expressionString); + final Expression expression = new ExpressionParser(mock(Edm.class), odata).parse(tokenizer, null, null, alias); + assertNotNull(expression); + + assertEquals('{' + methodName + ' ' + "[@word1, " + parameters[1] + ']' + '}', + expression.toString()); + + } + + private void parseMethodWithParametersWithAlias(TokenKind kind, + String... parameters) throws UriParserException, UriValidationException { + final String methodName = kind.name().substring(0, kind.name().indexOf("Method")).toLowerCase(Locale.ROOT) + .replace("geo", "geo."); + String expressionString = methodName + '('; + expressionString += "@word"; + expressionString += ','; + expressionString += parameters[1]; + expressionString += ')'; + expressionString += "&@word=" + parameters[0]; + + Map alias = new HashMap(); + AliasQueryOptionImpl aliasQueryOption = new AliasQueryOptionImpl(); + aliasQueryOption.setName("@word"); + aliasQueryOption.setText("\'a\'"); + alias.put("@word", aliasQueryOption); + UriTokenizer tokenizer = new UriTokenizer(expressionString); + final Expression expression = new ExpressionParser(mock(Edm.class), odata).parse(tokenizer, null, null, alias); + assertNotNull(expression); + + assertEquals('{' + methodName + ' ' + "[@word, " + parameters[1] + ']' + '}', + expression.toString()); + + } + private void parseMethod(TokenKind kind, String... parameters) throws UriParserException, UriValidationException { final String methodName = kind.name().substring(0, kind.name().indexOf("Method")).toLowerCase(Locale.ROOT) .replace("geo", "geo."); @@ -278,4 +375,309 @@ public class ExpressionParserTest { assertNotNull(e); } } + + @Test + public void testPropertyPathExp() throws Exception { + final String entitySetName = "ESName"; + final String keyPropertyName = "a"; + EdmProperty keyProperty = mockProperty(keyPropertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + EdmKeyPropertyRef keyPropertyRef = mockKeyPropertyRef(keyPropertyName, keyProperty); + EdmEntityType entityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(entityType.getPropertyNames()).thenReturn(Collections.singletonList(keyPropertyName)); + Mockito.when(entityType.getProperty(keyPropertyName)).thenReturn(keyProperty); + EdmEntitySet entitySet = mockEntitySet(entitySetName, entityType); + EdmEntityContainer container = mockContainer(entitySetName, entitySet); + Edm mockedEdm = Mockito.mock(Edm.class); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); + + UriTokenizer tokenizer = new UriTokenizer("a eq \'abc\'"); + final Expression expression = new ExpressionParser(mockedEdm, odata).parse(tokenizer, + entityType, null, null); + assertNotNull(expression); + assertEquals("{[a] EQ \'abc\'}", expression.toString()); + } + + /** + * @param keyPropertyName + * @param keyPropertyRef + * @return + */ + private EdmEntityType mockEntityType(final String keyPropertyName, EdmKeyPropertyRef keyPropertyRef) { + EdmEntityType entityType = Mockito.mock(EdmEntityType.class); + Mockito.when(entityType.getKeyPredicateNames()).thenReturn(Collections.singletonList(keyPropertyName)); + Mockito.when(entityType.getKeyPropertyRefs()).thenReturn(Collections.singletonList(keyPropertyRef)); + return entityType; + } + + /** + * @param keyPropertyName + * @param keyProperty + * @return + */ + private EdmKeyPropertyRef mockKeyPropertyRef(final String keyPropertyName, EdmProperty keyProperty) { + EdmKeyPropertyRef keyPropertyRef = Mockito.mock(EdmKeyPropertyRef.class); + Mockito.when(keyPropertyRef.getName()).thenReturn(keyPropertyName); + Mockito.when(keyPropertyRef.getProperty()).thenReturn(keyProperty); + return keyPropertyRef; + } + + /** + * @param propertyName + * @return + */ + private EdmProperty mockProperty(final String propertyName, final EdmType type) { + EdmProperty keyProperty = Mockito.mock(EdmProperty.class); + Mockito.when(keyProperty.getType()).thenReturn(type); + Mockito.when(keyProperty.getDefaultValue()).thenReturn(""); + Mockito.when(keyProperty.getName()).thenReturn(propertyName); + return keyProperty; + } + + @Test(expected = UriParserSemanticException.class) + public void testPropertyPathExpWithoutType() throws Exception { + final String entitySetName = "ESName"; + final String keyPropertyName = "a"; + EdmProperty keyProperty = mockProperty(keyPropertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + EdmKeyPropertyRef keyPropertyRef = mockKeyPropertyRef(keyPropertyName, keyProperty); + EdmEntityType entityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(entityType.getPropertyNames()).thenReturn(Collections.singletonList(keyPropertyName)); + Mockito.when(entityType.getProperty(keyPropertyName)).thenReturn(keyProperty); + EdmEntitySet entitySet = mockEntitySet(entitySetName, entityType); + EdmEntityContainer container = mockContainer(entitySetName, entitySet); + Edm mockedEdm = Mockito.mock(Edm.class); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); + + UriTokenizer tokenizer = new UriTokenizer("a eq \'abc\'"); + new ExpressionParser(mockedEdm, odata).parse(tokenizer, null, null, null); + } + + @Test(expected = UriParserSemanticException.class) + public void testPropertyPathExpWithoutProperty() throws Exception { + final String entitySetName = "ESName"; + final String keyPropertyName = "a"; + EdmProperty keyProperty = mockProperty(keyPropertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + EdmKeyPropertyRef keyPropertyRef = mockKeyPropertyRef(keyPropertyName, keyProperty); + EdmEntityType entityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(entityType.getFullQualifiedName()).thenReturn(new FullQualifiedName("test.ETName")); + EdmEntitySet entitySet = mockEntitySet(entitySetName, entityType); + EdmEntityContainer container = mockContainer(entitySetName, entitySet); + Edm mockedEdm = Mockito.mock(Edm.class); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); + + UriTokenizer tokenizer = new UriTokenizer("a eq \'abc\'"); + new ExpressionParser(mockedEdm, odata).parse(tokenizer, entityType, null, null); + } + + /** + * @param entitySetName + * @param entitySet + * @return + */ + private EdmEntityContainer mockContainer(final String entitySetName, EdmEntitySet entitySet) { + EdmEntityContainer container = Mockito.mock(EdmEntityContainer.class); + Mockito.when(container.getEntitySet(entitySetName)).thenReturn(entitySet); + return container; + } + + /** + * @param entitySetName + * @param entityType + * @return + */ + private EdmEntitySet mockEntitySet(final String entitySetName, EdmEntityType entityType) { + EdmEntitySet entitySet = Mockito.mock(EdmEntitySet.class); + Mockito.when(entitySet.getName()).thenReturn(entitySetName); + Mockito.when(entitySet.getEntityType()).thenReturn(entityType); + return entitySet; + } + + @Test + public void testComplexPropertyPathExp() throws Exception { + final String entitySetName = "ESName"; + final String keyPropertyName = "a"; + final String complexPropertyName = "comp"; + final String propertyName = "prop"; + EdmProperty keyProperty = mockProperty(keyPropertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + EdmKeyPropertyRef keyPropertyRef = mockKeyPropertyRef(keyPropertyName, keyProperty); + EdmProperty property = mockProperty(propertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + + EdmComplexType complexType = mockComplexType(propertyName, property); + EdmProperty complexProperty = mockProperty(complexPropertyName, complexType); + + EdmEntityType entityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(entityType.getPropertyNames()).thenReturn(Arrays.asList(keyPropertyName, complexPropertyName)); + Mockito.when(entityType.getProperty(keyPropertyName)).thenReturn(keyProperty); + Mockito.when(entityType.getProperty(complexPropertyName)).thenReturn(complexProperty); + EdmEntitySet entitySet = mockEntitySet(entitySetName, entityType); + EdmEntityContainer container = mockContainer(entitySetName, entitySet); + Edm mockedEdm = Mockito.mock(Edm.class); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); + + UriTokenizer tokenizer = new UriTokenizer("comp/prop eq \'abc\'"); + final Expression expression = new ExpressionParser(mockedEdm, odata).parse(tokenizer, + entityType, null, null); + assertNotNull(expression); + assertEquals("{[comp, prop] EQ \'abc\'}", expression.toString()); + } + + /** + * @param propertyName + * @param property + * @return + */ + private EdmComplexType mockComplexType(final String propertyName, EdmProperty property) { + EdmComplexType complexType = Mockito.mock(EdmComplexType.class); + Mockito.when(complexType.getPropertyNames()).thenReturn(Collections.singletonList(propertyName)); + Mockito.when(complexType.getProperty(propertyName)).thenReturn(property); + return complexType; + } + + /** + * @param propertyName + * @param property + * @return + */ + private EdmComplexType mockComplexType(final String propertyName, EdmNavigationProperty property) { + EdmComplexType complexType = Mockito.mock(EdmComplexType.class); + Mockito.when(complexType.getPropertyNames()).thenReturn(Collections.singletonList(propertyName)); + Mockito.when(complexType.getProperty(propertyName)).thenReturn(property); + return complexType; + } + + @Test + public void testLambdaPropertyPathExp() throws Exception { + final String entitySetName = "ESName"; + final String keyPropertyName = "a"; + final String complexPropertyName = "comp"; + final String propertyName = "prop"; + EdmProperty keyProperty = mockProperty(keyPropertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + EdmKeyPropertyRef keyPropertyRef = mockKeyPropertyRef(keyPropertyName, keyProperty); + EdmProperty property = mockProperty(propertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + + EdmComplexType complexType = mockComplexType(propertyName, property); + EdmProperty complexProperty = mockProperty(complexPropertyName, complexType); + Mockito.when(complexProperty.isCollection()).thenReturn(true); + + EdmEntityType entityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(entityType.getPropertyNames()).thenReturn(Arrays.asList(keyPropertyName, complexPropertyName)); + Mockito.when(entityType.getProperty(keyPropertyName)).thenReturn(keyProperty); + Mockito.when(entityType.getProperty(complexPropertyName)).thenReturn(complexProperty); + Mockito.when(entityType.getFullQualifiedName()).thenReturn(new FullQualifiedName("test.ET")); + EdmEntitySet entitySet = mockEntitySet(entitySetName, entityType); + EdmEntityContainer container = mockContainer(entitySetName, entitySet); + Edm mockedEdm = Mockito.mock(Edm.class); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); + + UriTokenizer tokenizer = new UriTokenizer("comp/any(d:d/prop eq \'abc\')"); + Expression expression = new ExpressionParser(mockedEdm, odata).parse(tokenizer, + entityType, null, null); + assertNotNull(expression); + assertEquals("[comp, any]", expression.toString()); + + tokenizer = new UriTokenizer("comp/all(d:d/prop eq \'abc\')"); + expression = new ExpressionParser(mockedEdm, odata).parse(tokenizer, + entityType, null, null); + assertNotNull(expression); + assertEquals("[comp, all]", expression.toString()); + } + + @Test + public void testNavigationPropertyPathExp() throws Exception { + final String entitySetName = "ESName"; + final String keyPropertyName = "a"; + final String complexPropertyName = "comp"; + final String propertyName = "navProp"; + EdmProperty keyProperty = mockProperty(keyPropertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + EdmKeyPropertyRef keyPropertyRef = mockKeyPropertyRef(keyPropertyName, keyProperty); + + EdmEntityType targetEntityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(targetEntityType.getFullQualifiedName()).thenReturn(new FullQualifiedName("test.TargetET")); + EdmNavigationProperty navProperty = mockNavigationProperty(propertyName, targetEntityType); + + EdmComplexType complexType = mockComplexType(propertyName, navProperty); + EdmProperty complexProperty = mockProperty(complexPropertyName, complexType); + + EdmEntityType startEntityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(startEntityType.getFullQualifiedName()).thenReturn(new FullQualifiedName("test.StartET")); + Mockito.when(startEntityType.getPropertyNames()).thenReturn( + Arrays.asList(keyPropertyName, complexPropertyName)); + Mockito.when(startEntityType.getProperty(keyPropertyName)).thenReturn(keyProperty); + Mockito.when(startEntityType.getProperty(complexPropertyName)).thenReturn(complexProperty); + EdmEntitySet entitySet = mockEntitySet(entitySetName, startEntityType); + EdmEntityContainer container = mockContainer(entitySetName, entitySet); + Edm mockedEdm = Mockito.mock(Edm.class); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); + + UriTokenizer tokenizer = new UriTokenizer("comp/navProp"); + final Expression expression = new ExpressionParser(mockedEdm, odata).parse(tokenizer, + startEntityType, null, null); + assertNotNull(expression); + assertEquals("[comp, navProp]", expression.toString()); + } + + /** + * @param propertyName + * @param entityType + * @return + */ + private EdmNavigationProperty mockNavigationProperty(final String propertyName, EdmEntityType entityType) { + EdmNavigationProperty navProperty = Mockito.mock(EdmNavigationProperty.class); + Mockito.when(navProperty.getName()).thenReturn(propertyName); + Mockito.when(navProperty.getType()).thenReturn(entityType); + return navProperty; + } + + @Test + public void testDerivedPathExp() throws Exception { + final String derivedEntitySetName = "ESName"; + final String keyPropertyName = "a"; + final String propertyName = "navProp"; + EdmProperty keyProperty = mockProperty(keyPropertyName, + OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String)); + EdmKeyPropertyRef keyPropertyRef = mockKeyPropertyRef(keyPropertyName, keyProperty); + + EdmEntityType navEntityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(navEntityType.getFullQualifiedName()).thenReturn(new FullQualifiedName("test.navET")); + Mockito.when(navEntityType.getNamespace()).thenReturn("test"); + Mockito.when(navEntityType.getPropertyNames()).thenReturn( + Arrays.asList(keyPropertyName)); + Mockito.when(navEntityType.getProperty(keyPropertyName)).thenReturn(keyProperty); + + EdmEntityType baseEntityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(baseEntityType.getFullQualifiedName()).thenReturn(new FullQualifiedName("test.baseET")); + Mockito.when(baseEntityType.getNamespace()).thenReturn("test"); + Mockito.when(baseEntityType.getPropertyNames()).thenReturn( + Arrays.asList(keyPropertyName)); + Mockito.when(baseEntityType.getProperty(keyPropertyName)).thenReturn(keyProperty); + + Mockito.when(navEntityType.getBaseType()).thenReturn(baseEntityType); + Mockito.when(baseEntityType.compatibleTo(navEntityType)).thenReturn(true); + + EdmEntityType entityType = mockEntityType(keyPropertyName, keyPropertyRef); + Mockito.when(entityType.getFullQualifiedName()).thenReturn(new FullQualifiedName("test.derivedET")); + Mockito.when(entityType.getNamespace()).thenReturn("test"); + Mockito.when(entityType.getPropertyNames()).thenReturn(Arrays.asList(keyPropertyName, propertyName)); + EdmNavigationProperty navProperty = mockNavigationProperty(propertyName, navEntityType); + Mockito.when(entityType.getProperty(propertyName)).thenReturn(navProperty); + + EdmEntitySet entitySet = mockEntitySet(derivedEntitySetName, entityType); + EdmEntityContainer container = mockContainer(derivedEntitySetName, entitySet); + Edm mockedEdm = Mockito.mock(Edm.class); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); + Mockito.when(mockedEdm.getEntityType(new FullQualifiedName("test.baseET"))).thenReturn(baseEntityType); + + UriTokenizer tokenizer = new UriTokenizer("navProp/test.baseET"); + Expression expression = new ExpressionParser(mockedEdm, odata).parse(tokenizer, + entityType, null, null); + assertNotNull(expression); + assertEquals("[navProp]", expression.toString()); + } } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java index 253dbde30..98b3f7f9a 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/ExpressionVisitorImpl.java @@ -182,6 +182,8 @@ public class ExpressionVisitorImpl implements ExpressionVisitor return methodCallOperation.floor(); case CEILING: return methodCallOperation.ceiling(); + case SUBSTRINGOF: + return methodCallOperation.substringof(); default: return throwNotImplemented(); diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/operation/MethodCallOperator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/operation/MethodCallOperator.java index 77185af81..70b81722b 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/operation/MethodCallOperator.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/expression/operation/MethodCallOperator.java @@ -108,6 +108,15 @@ public class MethodCallOperator { } }, primBoolean); } + + public VisitorOperand substringof() throws ODataApplicationException { + return stringFunction(new StringFunction() { + @Override + public Object perform(final List params) { + return params.get(1).contains(params.get(0)); + } + }, primBoolean); + } public VisitorOperand toLower() throws ODataApplicationException { return stringFunction(new StringFunction() { diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java index 6985e5124..63d74875e 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java @@ -50,6 +50,29 @@ public class ExpressionParserTest { @Test public void filter() throws Exception { + testFilter.runOnESCompCollComp("PropertyComp/CollPropertyComp/any" + + "(f:f/olingo.odata.test1.CTBase/AdditionalPropString eq 'ADD TEST')") + .is(" eq <'ADD TEST'>>>>") + .isMember().goPath() + .first() + .isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTCompCollComp, false) + .n() + .isComplexProperty("CollPropertyComp", ComplexTypeProvider.nameCTTwoPrim, true) + .n() + .isUriPathInfoKind(UriResourceKind.lambdaAny) + .goLambdaExpression() + .isBinary(BinaryOperatorKind.EQ) + .left().goPath() + .first().isUriPathInfoKind(UriResourceKind.lambdaVariable) + .isType(ComplexTypeProvider.nameCTTwoPrim, false) + .n() + .isPrimitiveProperty("AdditionalPropString", PropertyProvider.nameString, false) + .goUpFilterValidator() + .root().right() + .isLiteral("'ADD TEST'"); + + testFilter.runOnESCompCollCompNeg("PropertyComp/CollPropertyComp/olingo.odata.test1.BFCCTPrimCompRTESTwoKeyNav()"); + testFilter.runOnETAllPrim("PropertyBoolean") .is("") .isType(PropertyProvider.nameBoolean); @@ -1928,6 +1951,24 @@ public class ExpressionParserTest { .n().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTPrimComp, false) .n().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTAllPrim, false) .n().isPrimitiveProperty("PropertyDate", PropertyProvider.nameDate, false); + + testFilter.runOrderByOnETTwoKeyNav("PropertyComp/PropertyComp/PropertyDate eq " + + "$root/SINav/PropertyComp/PropertyComp/PropertyDate") + .isSortOrder(0, false) + .goOrder(0).isBinary(BinaryOperatorKind.EQ).left().goPath() + .first().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTPrimComp, false) + .n().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTAllPrim, false) + .n().isPrimitiveProperty("PropertyDate", PropertyProvider.nameDate, false) + .goUpFilterValidator() + .goOrder(0).right().goPath() + .first().isUriPathInfoKind(UriResourceKind.root) + .n().isSingleton("SINav") + .n().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTPrimComp, false) + .n().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTAllPrim, false) + .n().isPrimitiveProperty("PropertyDate", PropertyProvider.nameDate, false); + + testFilter.runOrderByOnETTwoKeyNavNeg("PropertyComp/PropertyComp/PropertyDate eq " + + "$root/SIType/PropertyComp/PropertyComp/PropertyDate"); testFilter.runOrderByOnETTwoKeyNav("PropertyComp/PropertyComp/PropertyDate eq 2013-11-12 desc," + "PropertyString eq 'SomeString' desc") diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java index 246a408c3..c68ecde94 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java @@ -51,6 +51,7 @@ import org.apache.olingo.server.api.uri.queryoption.expression.Unary; import org.apache.olingo.server.core.uri.UriResourceFunctionImpl; import org.apache.olingo.server.core.uri.parser.Parser; import org.apache.olingo.server.core.uri.parser.UriParserException; +import org.apache.olingo.server.core.uri.parser.UriParserSemanticException; import org.apache.olingo.server.core.uri.queryoption.expression.BinaryImpl; import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl; import org.apache.olingo.server.core.uri.queryoption.expression.MethodImpl; @@ -102,6 +103,16 @@ public class FilterValidator implements TestValidator { throws UriParserException, UriValidationException { return runUriOrderBy("ESTwoKeyNav", "$orderby=" + orderBy); } + + public FilterValidator runOrderByOnETTwoKeyNavNeg(final String orderBy) + throws UriParserException, UriValidationException { + try { + runUriOrderBy("ESTwoKeyNav", "$orderby=" + orderBy); + } catch (UriParserSemanticException e) { + assertEquals("EntitySet or singleton expected.", e.getMessage()); + } + return this; + } public FilterValidator runOrderByOnETMixEnumDefCollComp(final String orderBy) throws UriParserException, UriValidationException { @@ -133,6 +144,20 @@ public class FilterValidator implements TestValidator { public FilterValidator runOnETAllPrim(final String filter) throws UriParserException, UriValidationException { return runUri("ESAllPrim(1)", "$filter=" + filter); } + + public FilterValidator runOnESCompCollComp(final String filter) throws UriParserException, UriValidationException { + return runUri("ESCompCollComp", "$filter=" + filter); + } + + public FilterValidator runOnESCompCollCompNeg(final String filter) + throws UriParserException, UriValidationException { + try { + runUri("ESCompCollComp", "$filter=" + filter); + } catch (UriParserSemanticException e) { + assertEquals("Bound function 'olingo.odata.test1.BFCCTPrimCompRTESTwoKeyNav' not found.", e.getMessage()); + } + return this; + } public FilterValidator runOnETKeyNav(final String filter) throws UriParserException, UriValidationException { return runUri("ESKeyNav(1)", "$filter=" + filter);