From 8674a1f29919a9f2fe472cf4aa892344bb65e73f Mon Sep 17 00:00:00 2001 From: Klaus Straubinger Date: Fri, 20 Nov 2015 16:43:25 +0100 Subject: [PATCH] [OLINGO-568] improved search in technical service Signed-off-by: Christian Amend --- .../client/SystemQueryOptionITCase.java | 48 ++++----- .../processor/TechnicalEntityProcessor.java | 7 +- .../TechnicalPrimitiveComplexProcessor.java | 5 +- .../queryoptions/options/SearchHandler.java | 97 ++++++++++++------- 4 files changed, 94 insertions(+), 63 deletions(-) diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java index b33003842..7ffa8a9a5 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java @@ -19,10 +19,11 @@ package org.apache.olingo.fit.tecsvc.client; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.net.URI; -import java.util.List; import org.apache.olingo.client.api.communication.ODataClientErrorException; import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetRequest; @@ -83,7 +84,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { for (int i = 0; i < 5; i++) { ClientEntity entity = response.getBody().getEntities().get(i); - assertEquals(new Integer(i + 1).toString(), entity.getProperty(PROPERTY_INT16).getValue().toString()); + assertShortOrInt(i + 1, entity.getProperty(PROPERTY_INT16).getPrimitiveValue().toValue()); } } @@ -102,7 +103,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { for (int i = 0; i < 10; i++) { ClientEntity entity = response.getBody().getEntities().get(i); - assertEquals(new Integer(i + 6).toString(), entity.getProperty(PROPERTY_INT16).getValue().toString()); + assertShortOrInt(i + 6, entity.getProperty(PROPERTY_INT16).getPrimitiveValue().toValue()); } } @@ -118,7 +119,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { ODataRetrieveResponse response = request.execute(); saveCookieHeader(response); - assertEquals(0, response.getBody().getEntities().size()); + assertTrue(response.getBody().getEntities().isEmpty()); } @Test @@ -132,15 +133,16 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { ODataRetrieveResponse response = request.execute(); saveCookieHeader(response); - assertEquals(0, response.getBody().getEntities().size()); + assertTrue(response.getBody().getEntities().isEmpty()); } @Test - public void filterWithTopSkipOrderByAndServerSidePaging() { + public void searchAndFilterWithTopSkipOrderByAndServerSidePaging() { ODataEntitySetRequest request = getClient().getRetrieveRequestFactory() .getEntitySetRequest(getClient().newURIBuilder(SERVICE_URI) .appendEntitySetSegment(ES_SERVER_SIDE_PAGING) - .filter("PropertyInt16 le 105") // 1, 2, ... , 105 + .search("\"Number:\" AND NOT \"106\"") // 1, 2, ..., 105, 107, ... + .filter("PropertyInt16 le 106") // 1, 2, ..., 105 .orderBy("PropertyInt16 desc") // 105, 104, ..., 2, 1 .count(true) // 105 .skip(3) // 102, 101, ..., 2, 1 @@ -155,14 +157,14 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { int id = 102; - // Check first 10 entities + // Check first 10 entities. for (int i = 0; i < 10; i++) { ClientEntity entity = response.getBody().getEntities().get(i); - assertEquals(new Integer(id).toString(), entity.getProperty(PROPERTY_INT16).getValue().toString()); + assertShortOrInt(id, entity.getProperty(PROPERTY_INT16).getPrimitiveValue().toValue()); id--; } - // Get 3 * 10 = 30 Entities and check the key + // Get 3 * 10 = 30 Entities and check the key. for (int j = 0; j < 3; j++) { request = getClient().getRetrieveRequestFactory().getEntitySetRequest(response.getBody().getNext()); setCookieHeader(request); @@ -172,12 +174,12 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { assertEquals(10, response.getBody().getEntities().size()); for (int i = 0; i < 10; i++) { ClientEntity entity = response.getBody().getEntities().get(i); - assertEquals(new Integer(id).toString(), entity.getProperty(PROPERTY_INT16).getValue().toString()); + assertShortOrInt(id, entity.getProperty(PROPERTY_INT16).getPrimitiveValue().toValue()); id--; } } - // Get the last 3 items + // Get the last 3 items. request = getClient().getRetrieveRequestFactory().getEntitySetRequest(response.getBody().getNext()); setCookieHeader(request); response = request.execute(); @@ -186,12 +188,12 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { assertEquals(3, response.getBody().getEntities().size()); for (int i = 0; i < 3; i++) { ClientEntity entity = response.getBody().getEntities().get(i); - assertEquals(new Integer(id).toString(), entity.getProperty(PROPERTY_INT16).getValue().toString()); + assertShortOrInt(id, entity.getProperty(PROPERTY_INT16).getPrimitiveValue().toValue()); id--; } // Make sure that the body no not contain a next link - assertEquals(null, response.getBody().getNext()); + assertNull(response.getBody().getNext()); } @Test @@ -299,7 +301,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), e.getStatusLine().getStatusCode()); } } - + @Test public void basicSearch() { ODataEntitySetRequest request = getClient().getRetrieveRequestFactory() @@ -308,9 +310,9 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { .search("Second") .build()); setCookieHeader(request); - ODataRetrieveResponse response = request.execute(); - List entities = response.getBody().getEntities(); - assertEquals(1, entities.size()); + final ODataRetrieveResponse response = request.execute(); + saveCookieHeader(response); + assertEquals(1, response.getBody().getEntities().size()); } @Test @@ -321,9 +323,9 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { .search("Second AND positive") .build()); setCookieHeader(request); - ODataRetrieveResponse response = request.execute(); - List entities = response.getBody().getEntities(); - assertEquals(0, entities.size()); + final ODataRetrieveResponse response = request.execute(); + saveCookieHeader(response); + assertTrue(response.getBody().getEntities().isEmpty()); } @Test @@ -335,7 +337,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { .build()); setCookieHeader(request); ODataRetrieveResponse response = request.execute(); - List entities = response.getBody().getEntities(); - assertEquals(2, entities.size()); + saveCookieHeader(response); + assertEquals(2, response.getBody().getEntities().size()); } } 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 923bf293e..6d7c41ea1 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 @@ -471,23 +471,22 @@ public class TechnicalEntityProcessor extends TechnicalProcessor edmEntitySet.getEntityType(); EntityCollection entitySetInitial = readEntityCollection(uriInfo); - if (entitySetInitial == null) { entitySetInitial = new EntityCollection(); } // 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) + // copy of the entity set (new EntitySet, but exactly the same data). EntityCollection entitySet = new EntityCollection(); entitySet.getEntities().addAll(entitySetInitial.getEntities()); - // Apply system query options + // Apply system query options. + SearchHandler.applySearchSystemQueryOption(uriInfo.getSearchOption(), entitySet); FilterHandler.applyFilterSystemQuery(uriInfo.getFilterOption(), entitySet, uriInfo, serviceMetadata.getEdm()); CountHandler.applyCountSystemQueryOption(uriInfo.getCountOption(), entitySet); OrderByHandler.applyOrderByOption(uriInfo.getOrderByOption(), entitySet, uriInfo, serviceMetadata.getEdm()); SkipHandler.applySkipSystemQueryHandler(uriInfo.getSkipOption(), entitySet); TopHandler.applyTopSystemQueryOption(uriInfo.getTopOption(), entitySet); - SearchHandler.applySearchSystemQueryOption(uriInfo.getSearchOption(), entitySet); final Integer pageSize = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getMaxPageSize(); final Integer serverPageSize = ServerSidePagingHandler.applyServerSidePaging(uriInfo.getSkipTokenOption(), diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java index 87fbf00cd..f97b7eb7c 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java @@ -47,8 +47,8 @@ import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer; -import org.apache.olingo.server.api.prefer.PreferencesApplied; import org.apache.olingo.server.api.prefer.Preferences.Return; +import org.apache.olingo.server.api.prefer.PreferencesApplied; import org.apache.olingo.server.api.processor.ComplexCollectionProcessor; import org.apache.olingo.server.api.processor.ComplexProcessor; import org.apache.olingo.server.api.processor.CountComplexCollectionProcessor; @@ -226,6 +226,9 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor ((UriResourceFunction) resourceParts.get(0)).getParameters(), resource), path) : getPropertyData(entity, path); + // TODO: implement filter on collection properties (on a shallow copy of the values) + // FilterHandler.applyFilterSystemQuery(uriInfo.getFilterOption(), property, uriInfo, serviceMetadata.getEdm()); + if (property == null && representationType != RepresentationType.COUNT) { if (representationType == RepresentationType.VALUE) { response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/SearchHandler.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/SearchHandler.java index ccc165874..78b6a3b6b 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/SearchHandler.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/SearchHandler.java @@ -18,6 +18,13 @@ */ package org.apache.olingo.server.tecsvc.processor.queryoptions.options; +import java.util.Calendar; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Locale; + +import javax.xml.bind.DatatypeConverter; + import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Property; @@ -29,64 +36,83 @@ import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorK import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression; import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm; -import javax.xml.bind.DatatypeConverter; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.Iterator; -import java.util.ListIterator; -import java.util.Locale; - public class SearchHandler { - private static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - public static void applySearchSystemQueryOption(final SearchOption searchOption, final EntityCollection entitySet) + public static void applySearchSystemQueryOption(final SearchOption searchOption, EntityCollection entitySet) throws ODataApplicationException { - if (searchOption != null) { SearchExpression se = searchOption.getSearchExpression(); Iterator it = entitySet.getEntities().iterator(); - while(it.hasNext()) { + while (it.hasNext()) { boolean keep = false; Entity entity = it.next(); ListIterator properties = entity.getProperties().listIterator(); - while(properties.hasNext() && !keep) { + while (properties.hasNext() && !keep) { keep = isTrue(se, properties.next()); } - if(!keep) { + if (!keep) { it.remove(); } } } } - private static boolean isTrue(SearchTerm term, Property property) { - if(property.isPrimitive() && !property.isNull()) { - String propertyString = asString(property); - return propertyString != null && propertyString.contains(term.getSearchTerm()); + private static boolean isTrue(final SearchTerm term, final Property property) { + if (property.isNull()) { + return false; + } else if (property.isPrimitive()) { + if (property.isCollection()) { + for (final Object primitive : property.asCollection()) { + final String propertyString = asString(primitive); + if (propertyString != null && propertyString.contains(term.getSearchTerm())) { + return true; + } + } + return false; + } else { + final String propertyString = asString(property.asPrimitive()); + return propertyString != null && propertyString.contains(term.getSearchTerm()); + } + } else if (property.isComplex()) { + if (property.isCollection()) { + for (final Object member : property.asCollection()) { + if (isTrue(term, (Property) member)) { + return true; + } + } + return false; + } else { + for (final Property innerProperty : property.asComplex().getValue()) { + if (isTrue(term, innerProperty)) { + return true; + } + } + return false; + } + } else { + return false; } - return false; } - private static String asString(Property property) { - // TODO: mibo(151117): improve 'string' conversion - Object primitive = property.asPrimitive(); - if(primitive instanceof Calendar) { - return SIMPLE_DATE_FORMAT.format(((Calendar) primitive).getTime()); - } else if(primitive instanceof Date) { - return SIMPLE_DATE_FORMAT.format((Date) primitive); - } else if(primitive instanceof byte[]) { + private static String asString(final Object primitive) { + // TODO: improve 'string' conversion; maybe consider only String properties + if (primitive instanceof String) { + return (String) primitive; + } else if (primitive instanceof Calendar) { + return DatatypeConverter.printDateTime((Calendar) primitive); + } else if (primitive instanceof byte[]) { return DatatypeConverter.printBase64Binary((byte[]) primitive); + } else { + return primitive.toString(); } - return primitive.toString(); } - private static boolean isTrue(SearchBinary binary, Property property) throws ODataApplicationException { + private static boolean isTrue(final SearchBinary binary, final Property property) throws ODataApplicationException { SearchExpression left = binary.getLeftOperand(); SearchExpression right = binary.getRightOperand(); - if(binary.getOperator() == SearchBinaryOperatorKind.AND) { + if (binary.getOperator() == SearchBinaryOperatorKind.AND) { return isTrue(left, property) && isTrue(right, property); - } else if(binary.getOperator() == SearchBinaryOperatorKind.OR) { + } else if (binary.getOperator() == SearchBinaryOperatorKind.OR) { return isTrue(left, property) || isTrue(right, property); } else { throw new ODataApplicationException("Found unknown SearchBinaryOperatorKind: " + binary.getOperator(), @@ -94,12 +120,13 @@ public class SearchHandler { } } - private static boolean isTrue(SearchExpression searchExpression, Property property) throws ODataApplicationException { - if(searchExpression.isSearchBinary()) { + private static boolean isTrue(final SearchExpression searchExpression, final Property property) + throws ODataApplicationException { + if (searchExpression.isSearchBinary()) { return isTrue(searchExpression.asSearchBinary(), property); - } else if(searchExpression.isSearchTerm()) { + } else if (searchExpression.isSearchTerm()) { return isTrue(searchExpression.asSearchTerm(), property); - } else if(searchExpression.isSearchUnary()) { + } else if (searchExpression.isSearchUnary()) { return !isTrue(searchExpression.asSearchUnary().getOperand(), property); } throw new ODataApplicationException("Found unknown SearchExpression: " + searchExpression,