[OLINGO-568] improved search in technical service

Signed-off-by: Christian Amend <christian.amend@sap.com>
This commit is contained in:
Klaus Straubinger 2015-11-20 16:43:25 +01:00 committed by Christian Amend
parent 69ef9f5194
commit 8674a1f299
4 changed files with 94 additions and 63 deletions

View File

@ -19,10 +19,11 @@
package org.apache.olingo.fit.tecsvc.client; package org.apache.olingo.fit.tecsvc.client;
import static org.junit.Assert.assertEquals; 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 static org.junit.Assert.fail;
import java.net.URI; import java.net.URI;
import java.util.List;
import org.apache.olingo.client.api.communication.ODataClientErrorException; 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.request.retrieve.ODataEntitySetRequest;
@ -83,7 +84,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
ClientEntity entity = response.getBody().getEntities().get(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++) { for (int i = 0; i < 10; i++) {
ClientEntity entity = response.getBody().getEntities().get(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<ClientEntitySet> response = request.execute(); ODataRetrieveResponse<ClientEntitySet> response = request.execute();
saveCookieHeader(response); saveCookieHeader(response);
assertEquals(0, response.getBody().getEntities().size()); assertTrue(response.getBody().getEntities().isEmpty());
} }
@Test @Test
@ -132,15 +133,16 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
ODataRetrieveResponse<ClientEntitySet> response = request.execute(); ODataRetrieveResponse<ClientEntitySet> response = request.execute();
saveCookieHeader(response); saveCookieHeader(response);
assertEquals(0, response.getBody().getEntities().size()); assertTrue(response.getBody().getEntities().isEmpty());
} }
@Test @Test
public void filterWithTopSkipOrderByAndServerSidePaging() { public void searchAndFilterWithTopSkipOrderByAndServerSidePaging() {
ODataEntitySetRequest<ClientEntitySet> request = getClient().getRetrieveRequestFactory() ODataEntitySetRequest<ClientEntitySet> request = getClient().getRetrieveRequestFactory()
.getEntitySetRequest(getClient().newURIBuilder(SERVICE_URI) .getEntitySetRequest(getClient().newURIBuilder(SERVICE_URI)
.appendEntitySetSegment(ES_SERVER_SIDE_PAGING) .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 .orderBy("PropertyInt16 desc") // 105, 104, ..., 2, 1
.count(true) // 105 .count(true) // 105
.skip(3) // 102, 101, ..., 2, 1 .skip(3) // 102, 101, ..., 2, 1
@ -155,14 +157,14 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
int id = 102; int id = 102;
// Check first 10 entities // Check first 10 entities.
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
ClientEntity entity = response.getBody().getEntities().get(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--; 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++) { for (int j = 0; j < 3; j++) {
request = getClient().getRetrieveRequestFactory().getEntitySetRequest(response.getBody().getNext()); request = getClient().getRetrieveRequestFactory().getEntitySetRequest(response.getBody().getNext());
setCookieHeader(request); setCookieHeader(request);
@ -172,12 +174,12 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
assertEquals(10, response.getBody().getEntities().size()); assertEquals(10, response.getBody().getEntities().size());
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
ClientEntity entity = response.getBody().getEntities().get(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--; id--;
} }
} }
// Get the last 3 items // Get the last 3 items.
request = getClient().getRetrieveRequestFactory().getEntitySetRequest(response.getBody().getNext()); request = getClient().getRetrieveRequestFactory().getEntitySetRequest(response.getBody().getNext());
setCookieHeader(request); setCookieHeader(request);
response = request.execute(); response = request.execute();
@ -186,12 +188,12 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
assertEquals(3, response.getBody().getEntities().size()); assertEquals(3, response.getBody().getEntities().size());
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
ClientEntity entity = response.getBody().getEntities().get(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--; id--;
} }
// Make sure that the body no not contain a next link // Make sure that the body no not contain a next link
assertEquals(null, response.getBody().getNext()); assertNull(response.getBody().getNext());
} }
@Test @Test
@ -308,9 +310,9 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
.search("Second") .search("Second")
.build()); .build());
setCookieHeader(request); setCookieHeader(request);
ODataRetrieveResponse<ClientEntitySet> response = request.execute(); final ODataRetrieveResponse<ClientEntitySet> response = request.execute();
List<ClientEntity> entities = response.getBody().getEntities(); saveCookieHeader(response);
assertEquals(1, entities.size()); assertEquals(1, response.getBody().getEntities().size());
} }
@Test @Test
@ -321,9 +323,9 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
.search("Second AND positive") .search("Second AND positive")
.build()); .build());
setCookieHeader(request); setCookieHeader(request);
ODataRetrieveResponse<ClientEntitySet> response = request.execute(); final ODataRetrieveResponse<ClientEntitySet> response = request.execute();
List<ClientEntity> entities = response.getBody().getEntities(); saveCookieHeader(response);
assertEquals(0, entities.size()); assertTrue(response.getBody().getEntities().isEmpty());
} }
@Test @Test
@ -335,7 +337,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
.build()); .build());
setCookieHeader(request); setCookieHeader(request);
ODataRetrieveResponse<ClientEntitySet> response = request.execute(); ODataRetrieveResponse<ClientEntitySet> response = request.execute();
List<ClientEntity> entities = response.getBody().getEntities(); saveCookieHeader(response);
assertEquals(2, entities.size()); assertEquals(2, response.getBody().getEntities().size());
} }
} }

View File

@ -471,23 +471,22 @@ public class TechnicalEntityProcessor extends TechnicalProcessor
edmEntitySet.getEntityType(); edmEntitySet.getEntityType();
EntityCollection entitySetInitial = readEntityCollection(uriInfo); EntityCollection entitySetInitial = readEntityCollection(uriInfo);
if (entitySetInitial == null) { if (entitySetInitial == null) {
entitySetInitial = new EntityCollection(); entitySetInitial = new EntityCollection();
} }
// Modifying the original entitySet means modifying the "database", so we have to make a shallow // 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(); EntityCollection entitySet = new EntityCollection();
entitySet.getEntities().addAll(entitySetInitial.getEntities()); 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()); FilterHandler.applyFilterSystemQuery(uriInfo.getFilterOption(), entitySet, uriInfo, serviceMetadata.getEdm());
CountHandler.applyCountSystemQueryOption(uriInfo.getCountOption(), entitySet); CountHandler.applyCountSystemQueryOption(uriInfo.getCountOption(), entitySet);
OrderByHandler.applyOrderByOption(uriInfo.getOrderByOption(), entitySet, uriInfo, serviceMetadata.getEdm()); OrderByHandler.applyOrderByOption(uriInfo.getOrderByOption(), entitySet, uriInfo, serviceMetadata.getEdm());
SkipHandler.applySkipSystemQueryHandler(uriInfo.getSkipOption(), entitySet); SkipHandler.applySkipSystemQueryHandler(uriInfo.getSkipOption(), entitySet);
TopHandler.applyTopSystemQueryOption(uriInfo.getTopOption(), entitySet); TopHandler.applyTopSystemQueryOption(uriInfo.getTopOption(), entitySet);
SearchHandler.applySearchSystemQueryOption(uriInfo.getSearchOption(), entitySet);
final Integer pageSize = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getMaxPageSize(); final Integer pageSize = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getMaxPageSize();
final Integer serverPageSize = ServerSidePagingHandler.applyServerSidePaging(uriInfo.getSkipTokenOption(), final Integer serverPageSize = ServerSidePagingHandler.applyServerSidePaging(uriInfo.getSkipTokenOption(),

View File

@ -47,8 +47,8 @@ import org.apache.olingo.server.api.ODataRequest;
import org.apache.olingo.server.api.ODataResponse; import org.apache.olingo.server.api.ODataResponse;
import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer; 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.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.ComplexCollectionProcessor;
import org.apache.olingo.server.api.processor.ComplexProcessor; import org.apache.olingo.server.api.processor.ComplexProcessor;
import org.apache.olingo.server.api.processor.CountComplexCollectionProcessor; import org.apache.olingo.server.api.processor.CountComplexCollectionProcessor;
@ -226,6 +226,9 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor
((UriResourceFunction) resourceParts.get(0)).getParameters(), resource), path) : ((UriResourceFunction) resourceParts.get(0)).getParameters(), resource), path) :
getPropertyData(entity, 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 (property == null && representationType != RepresentationType.COUNT) {
if (representationType == RepresentationType.VALUE) { if (representationType == RepresentationType.VALUE) {
response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());

View File

@ -18,6 +18,13 @@
*/ */
package org.apache.olingo.server.tecsvc.processor.queryoptions.options; 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.Entity;
import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.data.Property; 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.SearchExpression;
import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm; 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 { 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 { throws ODataApplicationException {
if (searchOption != null) { if (searchOption != null) {
SearchExpression se = searchOption.getSearchExpression(); SearchExpression se = searchOption.getSearchExpression();
Iterator<Entity> it = entitySet.getEntities().iterator(); Iterator<Entity> it = entitySet.getEntities().iterator();
while(it.hasNext()) { while (it.hasNext()) {
boolean keep = false; boolean keep = false;
Entity entity = it.next(); Entity entity = it.next();
ListIterator<Property> properties = entity.getProperties().listIterator(); ListIterator<Property> properties = entity.getProperties().listIterator();
while(properties.hasNext() && !keep) { while (properties.hasNext() && !keep) {
keep = isTrue(se, properties.next()); keep = isTrue(se, properties.next());
} }
if(!keep) { if (!keep) {
it.remove(); it.remove();
} }
} }
} }
} }
private static boolean isTrue(SearchTerm term, Property property) { private static boolean isTrue(final SearchTerm term, final Property property) {
if(property.isPrimitive() && !property.isNull()) { if (property.isNull()) {
String propertyString = asString(property); 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()); 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; return false;
} }
} else {
private static String asString(Property property) { return false;
// 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[]) {
return DatatypeConverter.printBase64Binary((byte[]) primitive);
} }
}
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 left = binary.getLeftOperand();
SearchExpression right = binary.getRightOperand(); SearchExpression right = binary.getRightOperand();
if(binary.getOperator() == SearchBinaryOperatorKind.AND) { if (binary.getOperator() == SearchBinaryOperatorKind.AND) {
return isTrue(left, property) && isTrue(right, property); 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); return isTrue(left, property) || isTrue(right, property);
} else { } else {
throw new ODataApplicationException("Found unknown SearchBinaryOperatorKind: " + binary.getOperator(), 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 { private static boolean isTrue(final SearchExpression searchExpression, final Property property)
if(searchExpression.isSearchBinary()) { throws ODataApplicationException {
if (searchExpression.isSearchBinary()) {
return isTrue(searchExpression.asSearchBinary(), property); return isTrue(searchExpression.asSearchBinary(), property);
} else if(searchExpression.isSearchTerm()) { } else if (searchExpression.isSearchTerm()) {
return isTrue(searchExpression.asSearchTerm(), property); return isTrue(searchExpression.asSearchTerm(), property);
} else if(searchExpression.isSearchUnary()) { } else if (searchExpression.isSearchUnary()) {
return !isTrue(searchExpression.asSearchUnary().getOperand(), property); return !isTrue(searchExpression.asSearchUnary().getOperand(), property);
} }
throw new ODataApplicationException("Found unknown SearchExpression: " + searchExpression, throw new ODataApplicationException("Found unknown SearchExpression: " + searchExpression,