From e5fe04aeeb1efb22404eec493fc341bf4cd0f251 Mon Sep 17 00:00:00 2001 From: Klaus Straubinger Date: Tue, 16 Jun 2015 16:47:39 +0200 Subject: [PATCH] [OLINGO-698] technical service supports preferences, part 1 Signed-off-by: Christian Amend --- .../fit/tecsvc/client/ActionImportITCase.java | 201 +++++++++--------- .../olingo/fit/tecsvc/client/BasicITCase.java | 127 ++++++----- .../tecsvc/client/PrimitiveComplexITCase.java | 16 ++ .../client/SystemQueryOptionITCase.java | 23 +- .../olingo/commons/api/http/HttpHeader.java | 15 +- .../processor/TechnicalActionProcessor.java | 180 ++++++++++------ .../processor/TechnicalEntityProcessor.java | 69 ++++-- .../TechnicalPrimitiveComplexProcessor.java | 29 ++- .../options/ServerSidePagingHandler.java | 125 ++++++----- 9 files changed, 486 insertions(+), 299 deletions(-) diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/ActionImportITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/ActionImportITCase.java index 0c40003c4..0e68a4ab7 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/ActionImportITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/ActionImportITCase.java @@ -25,6 +25,7 @@ import static org.junit.Assert.fail; import java.math.BigDecimal; import java.net.URI; import java.util.Calendar; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -32,6 +33,8 @@ import java.util.TimeZone; import org.apache.olingo.client.api.ODataClient; import org.apache.olingo.client.api.communication.ODataClientErrorException; +import org.apache.olingo.client.api.communication.header.HeaderName; +import org.apache.olingo.client.api.communication.request.invoke.ODataInvokeRequest; import org.apache.olingo.client.api.communication.response.ODataInvokeResponse; import org.apache.olingo.client.api.domain.ClientCollectionValue; import org.apache.olingo.client.api.domain.ClientComplexValue; @@ -42,6 +45,7 @@ import org.apache.olingo.client.api.domain.ClientValue; import org.apache.olingo.client.core.ODataClientFactory; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; 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.Test; @@ -50,50 +54,62 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void noReturnTypeAction() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRT").build(); - ODataInvokeResponse response = - getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class).execute(); - assertEquals(204, response.getStatusCode()); - } - - @Test - public void primitveAction() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTString").build(); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRT").build(); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class).execute(); - assertEquals(200, response.getStatusCode()); + assertEquals(HttpStatusCode.NO_CONTENT.getStatusCode(), response.getStatusCode()); + } + + @Test + public void primitiveAction() throws Exception { + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTString").build(); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class).execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); assertEquals("UARTString string value", response.getBody().getPrimitiveValue().toValue()); } @Test - public void primitveActionInvalidParameters() throws Exception { - Map parameters = new HashMap(); - parameters.put("Invalid", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt32(1)); - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTString").build(); + public void primitiveActionMinimalResponse() throws Exception { + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTString").build(); + ODataInvokeRequest request = getClient().getInvokeRequestFactory() + .getActionInvokeRequest(actionURI, ClientProperty.class); + request.setPrefer(getClient().newPreferences().returnMinimal()); + final ODataInvokeResponse response = request.execute(); + assertEquals(HttpStatusCode.NO_CONTENT.getStatusCode(), response.getStatusCode()); + assertEquals("return=\"minimal\"", response.getHeader(HeaderName.preferenceApplied).iterator().next()); + } + + @Test + public void primitiveActionInvalidParameters() throws Exception { + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTString").build(); + Map parameters = Collections.singletonMap("Invalid", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt32(1)); try { getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class, parameters) - .execute(); + .execute(); fail("Expected an ODataClientErrorException"); } catch (ODataClientErrorException e) { - assertEquals(400, e.getStatusLine().getStatusCode()); + assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), e.getStatusLine().getStatusCode()); } } @Test - public void primitveCollectionAction() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollStringTwoParam").build(); + public void primitiveCollectionAction() throws Exception { + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollStringTwoParam").build(); Map parameters = new HashMap(); parameters.put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 3)); - parameters.put("ParameterDuration", getClient().getObjectFactory().newPrimitiveValueBuilder().setType( - EdmPrimitiveTypeKind.Duration).setValue(new BigDecimal(1)).build()); + parameters.put("ParameterDuration", getClient().getObjectFactory().newPrimitiveValueBuilder() + .setType(EdmPrimitiveTypeKind.Duration).setValue(new BigDecimal(1)).build()); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientCollectionValue valueArray = response.getBody().getCollectionValue(); assertEquals(3, valueArray.size()); Iterator iterator = valueArray.iterator(); @@ -104,14 +120,14 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void complexAction() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCTTwoPrimParam").build(); - Map parameters = new HashMap(); - parameters.put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 3)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCTTwoPrimParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 3)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientComplexValue complexValue = response.getBody().getComplexValue(); ClientProperty propInt16 = complexValue.get("PropertyInt16"); assertNotNull(propInt16); @@ -123,28 +139,28 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void complexCollectionActionNoContent() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollCTTwoPrimParam").build(); - Map parameters = new HashMap(); - parameters.put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 0)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollCTTwoPrimParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 0)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientCollectionValue complexValueCollection = response.getBody().getCollectionValue(); assertEquals(0, complexValueCollection.size()); } @Test public void complexCollectionActionSubContent() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollCTTwoPrimParam").build(); - Map parameters = new HashMap(); - parameters.put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 1)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollCTTwoPrimParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 1)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientCollectionValue complexValueCollection = response.getBody().getCollectionValue(); assertEquals(1, complexValueCollection.size()); Iterator iterator = complexValueCollection.iterator(); @@ -156,14 +172,14 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void complexCollectionActionAllContent() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollCTTwoPrimParam").build(); - Map parameters = new HashMap(); - parameters.put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 3)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollCTTwoPrimParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 3)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientProperty.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientCollectionValue complexValueCollection = response.getBody().getCollectionValue(); assertEquals(3, complexValueCollection.size()); Iterator iterator = complexValueCollection.iterator(); @@ -183,15 +199,14 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void entityActionETTwoKeyTwoPrim() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTETTwoKeyTwoPrimParam").build(); - Map parameters = new HashMap(); - parameters - .put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) -365)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTETTwoKeyTwoPrimParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) -365)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientEntity.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientEntity entity = response.getBody(); ClientProperty propInt16 = entity.getProperty("PropertyInt16"); assertNotNull(propInt16); @@ -203,15 +218,14 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void entityCollectionActionETKeyNav() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollETKeyNavParam").build(); - Map parameters = new HashMap(); - parameters - .put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 3)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollETKeyNavParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 3)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientEntitySet.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientEntitySet entitySet = response.getBody(); assertEquals(3, entitySet.getEntities().size()); Integer key = 1; @@ -223,51 +237,49 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void entityCollectionActionETKeyNavEmptyCollection() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollETKeyNavParam").build(); - Map parameters = new HashMap(); - parameters - .put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 0)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollETKeyNavParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 0)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientEntitySet.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientEntitySet entitySet = response.getBody(); assertEquals(0, entitySet.getEntities().size()); } @Test public void entityCollectionActionETKeyNavNegativeParam() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollETKeyNavParam").build(); - Map parameters = new HashMap(); - parameters - .put("ParameterInt16", getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) -10)); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollETKeyNavParam").build(); + Map parameters = Collections.singletonMap("ParameterInt16", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) -10)); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientEntitySet.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientEntitySet entitySet = response.getBody(); assertEquals(0, entitySet.getEntities().size()); } @Test public void entityCollectionActionETAllPrim() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTCollESAllPrimParam").build(); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTCollESAllPrimParam").build(); Calendar time = Calendar.getInstance(TimeZone.getTimeZone("GMT")); time.clear(); time.set(Calendar.HOUR_OF_DAY, 3); time.set(Calendar.MINUTE, 0); time.set(Calendar.SECOND, 0); - Map parameters = new HashMap(); - parameters - .put("ParameterTimeOfDay", getClient().getObjectFactory().newPrimitiveValueBuilder().setType( - EdmPrimitiveTypeKind.TimeOfDay).setValue(time).build()); + Map parameters = Collections.singletonMap( + "ParameterTimeOfDay", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder() + .setType(EdmPrimitiveTypeKind.TimeOfDay).setValue(time).build()); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientEntitySet.class, parameters) - .execute(); - assertEquals(200, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); ClientEntitySet entitySet = response.getBody(); assertEquals(3, entitySet.getEntities().size()); Integer key = 1; @@ -279,20 +291,19 @@ public class ActionImportITCase extends AbstractBaseTestITCase { @Test public void entityActionETAllPrim() throws Exception { - URI actionURI = - getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTESAllPrimParam").build(); + final URI actionURI = getClient().newURIBuilder(TecSvcConst.BASE_URI) + .appendActionCallSegment("AIRTESAllPrimParam").build(); Calendar dateTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")); dateTime.clear(); dateTime.set(1012, 2, 0, 0, 0, 0); - Map parameters = new HashMap(); - parameters - .put("ParameterDate", getClient().getObjectFactory().newPrimitiveValueBuilder().setType( - EdmPrimitiveTypeKind.Date).setValue(dateTime).build()); + Map parameters = Collections.singletonMap( + "ParameterDate", + (ClientValue) getClient().getObjectFactory().newPrimitiveValueBuilder() + .setType(EdmPrimitiveTypeKind.Date).setValue(dateTime).build()); ODataInvokeResponse response = getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ClientEntity.class, parameters) - .execute(); - // Check 201 - assertEquals(201, response.getStatusCode()); + .execute(); + assertEquals(HttpStatusCode.CREATED.getStatusCode(), response.getStatusCode()); } @Override diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java index 819aab7d1..0f3a0995a 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java @@ -37,6 +37,7 @@ import java.util.List; import org.apache.olingo.client.api.EdmEnabledODataClient; import org.apache.olingo.client.api.ODataClient; import org.apache.olingo.client.api.communication.ODataClientErrorException; +import org.apache.olingo.client.api.communication.header.HeaderName; import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest; import org.apache.olingo.client.api.communication.request.cud.ODataEntityCreateRequest; import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest; @@ -400,6 +401,24 @@ public class BasicITCase extends AbstractBaseTestITCase { assertNull(property2.getPrimitiveValue()); } + @Test + public void createEntityMinimalResponse() throws Exception { + final ODataClient client = getClient(); + final ClientObjectFactory factory = client.getObjectFactory(); + ClientEntity newEntity = factory.newEntity(new FullQualifiedName("olingo.odata.test1", "ETTwoPrim")); + newEntity.getProperties().add(factory.newPrimitiveProperty("PropertyString", + factory.newPrimitiveValueBuilder().buildString("new"))); + ODataEntityCreateRequest request = client.getCUDRequestFactory().getEntityCreateRequest( + client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESTwoPrim").build(), + newEntity); + request.setPrefer(getClient().newPreferences().returnMinimal()); + + final ODataEntityCreateResponse response = request.execute(); + assertEquals(HttpStatusCode.NO_CONTENT.getStatusCode(), response.getStatusCode()); + assertEquals("return=\"minimal\"", response.getHeader(HeaderName.preferenceApplied).iterator().next()); + assertEquals(SERVICE_URI + "/ESTwoPrim(1)", response.getHeader(HttpHeader.LOCATION).iterator().next()); + } + @Test public void readEntityWithExpandedNavigationProperty() { final ODataClient client = ODataClientFactory.getEdmEnabledClient(SERVICE_URI); @@ -459,30 +478,20 @@ public class BasicITCase extends AbstractBaseTestITCase { entity.getProperties().add( of.newCollectionProperty("CollPropertyComp", of.newCollectionValue("CTPrimComp") - .add( - of.newComplexValue("CTPrimComp") - .add( - of.newPrimitiveProperty("PropertyInt16", of.newPrimitiveValueBuilder() - .buildInt16( - (short) 42))) - .add( - of.newComplexProperty("PropertyComp", of.newComplexValue("CTAllPrim") - .add( - of.newPrimitiveProperty("PropertyString", of - .newPrimitiveValueBuilder() - .buildString("42")))))) - .add( - of.newComplexValue("CTPrimComp") - .add( - of.newPrimitiveProperty("PropertyInt16", of.newPrimitiveValueBuilder() - .buildInt16( - (short) 43))) - .add( - of.newComplexProperty("PropertyComp", of.newComplexValue("CTAllPrim") - .add( - of.newPrimitiveProperty("PropertyString", of - .newPrimitiveValueBuilder() - .buildString("43")))))))); + .add(of.newComplexValue("CTPrimComp") + .add(of.newPrimitiveProperty("PropertyInt16", + of.newPrimitiveValueBuilder().buildInt16((short) 42))) + .add(of.newComplexProperty("PropertyComp", + of.newComplexValue("CTAllPrim") + .add(of.newPrimitiveProperty("PropertyString", + of.newPrimitiveValueBuilder().buildString("42")))))) + .add(of.newComplexValue("CTPrimComp") + .add(of.newPrimitiveProperty("PropertyInt16", + of.newPrimitiveValueBuilder().buildInt16((short) 43))) + .add(of.newComplexProperty("PropertyComp", + of.newComplexValue("CTAllPrim") + .add(of.newPrimitiveProperty("PropertyString", + of.newPrimitiveValueBuilder().buildString("43")))))))); final URI uri = getClient().newURIBuilder(SERVICE_URI) .appendEntitySetSegment("ESKeyNav") @@ -681,13 +690,15 @@ public class BasicITCase extends AbstractBaseTestITCase { entity.getProperties().add(of.newPrimitiveProperty("PropertyString", of.newPrimitiveValueBuilder() .buildString(null))); - final ODataEntityUpdateResponse updateResponse = client.getCUDRequestFactory() - .getEntityUpdateRequest(targetURI, UpdateType.PATCH, entity) - .execute(); + ODataEntityUpdateRequest request = client.getCUDRequestFactory() + .getEntityUpdateRequest(targetURI, UpdateType.PATCH, entity); + request.setPrefer(getClient().newPreferences().returnRepresentation()); + final ODataEntityUpdateResponse response = request.execute(); - assertEquals(HttpStatusCode.OK.getStatusCode(), updateResponse.getStatusCode()); - assertTrue(updateResponse.getBody().getProperty("PropertyString").hasNullValue()); - assertEquals(34, updateResponse.getBody().getProperty("PropertyDecimal").getPrimitiveValue().toValue()); + assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); + assertEquals("return=\"representation\"", response.getHeader(HeaderName.preferenceApplied).iterator().next()); + assertTrue(response.getBody().getProperty("PropertyString").hasNullValue()); + assertEquals(34, response.getBody().getProperty("PropertyDecimal").getPrimitiveValue().toValue()); } @Test(expected = ODataClientErrorException.class) @@ -743,23 +754,23 @@ public class BasicITCase extends AbstractBaseTestITCase { .expand("NavPropertyETKeyNavOne", "NavPropertyETKeyNavMany") .build()); entityRequest.addCustomHeader(HttpHeader.COOKIE, cookie); - final ODataRetrieveResponse entitytResponse = entityRequest.execute(); + final ODataRetrieveResponse entityResponse = entityRequest.execute(); - assertEquals(HttpStatusCode.OK.getStatusCode(), entitytResponse.getStatusCode()); - assertEquals(1, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavOne") + assertEquals(HttpStatusCode.OK.getStatusCode(), entityResponse.getStatusCode()); + assertEquals(1, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavOne") .asInlineEntity() .getEntity() .getProperty("PropertyInt16") .getPrimitiveValue() .toValue()); - assertEquals(3, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(3, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() .size()); - assertEquals(1, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(1, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() @@ -768,7 +779,7 @@ public class BasicITCase extends AbstractBaseTestITCase { .getPrimitiveValue() .toValue()); - assertEquals(2, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(2, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() @@ -777,7 +788,7 @@ public class BasicITCase extends AbstractBaseTestITCase { .getPrimitiveValue() .toValue()); - assertEquals(3, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(3, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() @@ -786,7 +797,7 @@ public class BasicITCase extends AbstractBaseTestITCase { .getPrimitiveValue() .toValue()); - final Iterator collectionIterator = entitytResponse.getBody() + final Iterator collectionIterator = entityResponse.getBody() .getProperty("CollPropertyString") .getCollectionValue() .iterator(); @@ -794,7 +805,7 @@ public class BasicITCase extends AbstractBaseTestITCase { assertEquals("Single entry!", collectionIterator.next().asPrimitive().toValue()); assertFalse(collectionIterator.hasNext()); - final ClientComplexValue complexValue = entitytResponse.getBody() + final ClientComplexValue complexValue = entityResponse.getBody() .getProperty("PropertyCompAllPrim") .getComplexValue(); @@ -825,14 +836,14 @@ public class BasicITCase extends AbstractBaseTestITCase { .add(of.newPrimitiveValueBuilder().buildString("Single entry!")))); entity.getProperties().add(of.newComplexProperty("PropertyCompAllPrim", of.newComplexValue("CTAllPrim") - .add(of.newPrimitiveProperty("PropertyString", - of.newPrimitiveValueBuilder().buildString("Changed"))))); + .add(of.newPrimitiveProperty("PropertyString", of.newPrimitiveValueBuilder().buildString("Changed"))))); - final ODataEntityUpdateResponse response = client.getCUDRequestFactory() - .getEntityUpdateRequest(targetURI, UpdateType.REPLACE, entity) - .execute(); - - assertEquals(HttpStatusCode.OK.getStatusCode(), response.getStatusCode()); + ODataEntityUpdateRequest request = client.getCUDRequestFactory() + .getEntityUpdateRequest(targetURI, UpdateType.REPLACE, entity); + request.setPrefer(getClient().newPreferences().returnMinimal()); + final ODataEntityUpdateResponse response = request.execute(); + assertEquals(HttpStatusCode.NO_CONTENT.getStatusCode(), response.getStatusCode()); + assertEquals("return=\"minimal\"", response.getHeader(HeaderName.preferenceApplied).iterator().next()); final String cookie = response.getHeader(HttpHeader.SET_COOKIE).iterator().next(); final ODataEntityRequest entityRequest = client.getRetrieveRequestFactory() @@ -843,23 +854,23 @@ public class BasicITCase extends AbstractBaseTestITCase { .expand("NavPropertyETKeyNavOne", "NavPropertyETKeyNavMany") .build()); entityRequest.addCustomHeader(HttpHeader.COOKIE, cookie); - final ODataRetrieveResponse entitytResponse = entityRequest.execute(); + final ODataRetrieveResponse entityResponse = entityRequest.execute(); - assertEquals(HttpStatusCode.OK.getStatusCode(), entitytResponse.getStatusCode()); - assertEquals(1, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavOne") + assertEquals(HttpStatusCode.OK.getStatusCode(), entityResponse.getStatusCode()); + assertEquals(1, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavOne") .asInlineEntity() .getEntity() .getProperty("PropertyInt16") .getPrimitiveValue() .toValue()); - assertEquals(3, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(3, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() .size()); - assertEquals(1, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(1, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() @@ -868,7 +879,7 @@ public class BasicITCase extends AbstractBaseTestITCase { .getPrimitiveValue() .toValue()); - assertEquals(2, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(2, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() @@ -877,7 +888,7 @@ public class BasicITCase extends AbstractBaseTestITCase { .getPrimitiveValue() .toValue()); - assertEquals(3, entitytResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") + assertEquals(3, entityResponse.getBody().getNavigationLink("NavPropertyETKeyNavMany") .asInlineEntitySet() .getEntitySet() .getEntities() @@ -886,7 +897,7 @@ public class BasicITCase extends AbstractBaseTestITCase { .getPrimitiveValue() .toValue()); - final Iterator collectionIterator = entitytResponse.getBody() + final Iterator collectionIterator = entityResponse.getBody() .getProperty("CollPropertyString") .getCollectionValue() .iterator(); @@ -894,7 +905,7 @@ public class BasicITCase extends AbstractBaseTestITCase { assertEquals("Single entry!", collectionIterator.next().asPrimitive().toValue()); assertFalse(collectionIterator.hasNext()); - final ClientComplexValue propCompAllPrim = entitytResponse.getBody() + final ClientComplexValue propCompAllPrim = entityResponse.getBody() .getProperty("PropertyCompAllPrim") .getComplexValue(); @@ -902,15 +913,15 @@ public class BasicITCase extends AbstractBaseTestITCase { assertTrue(propCompAllPrim.get("PropertyInt16").hasNullValue()); assertTrue(propCompAllPrim.get("PropertyDate").hasNullValue()); - final ClientComplexValue propCompTwoPrim = entitytResponse.getBody() + final ClientComplexValue propCompTwoPrim = entityResponse.getBody() .getProperty("PropertyCompTwoPrim") .getComplexValue(); assertEquals("Must not be null", propCompTwoPrim.get("PropertyString").getPrimitiveValue().toValue()); assertEquals(42, propCompTwoPrim.get("PropertyInt16").getPrimitiveValue().toValue()); - assertNotNull(entitytResponse.getBody().getProperty("PropertyCompNav").getComplexValue()); - assertTrue(entitytResponse.getBody() + assertNotNull(entityResponse.getBody().getProperty("PropertyCompNav").getComplexValue()); + assertTrue(entityResponse.getBody() .getProperty("PropertyCompNav") .getComplexValue() .get("PropertyInt16") diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/PrimitiveComplexITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/PrimitiveComplexITCase.java index 1dbd0e616..485aace87 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/PrimitiveComplexITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/PrimitiveComplexITCase.java @@ -33,6 +33,7 @@ import java.util.Iterator; import org.apache.commons.io.IOUtils; import org.apache.olingo.client.api.ODataClient; import org.apache.olingo.client.api.communication.ODataClientErrorException; +import org.apache.olingo.client.api.communication.header.HeaderName; import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest; import org.apache.olingo.client.api.communication.request.cud.ODataPropertyUpdateRequest; import org.apache.olingo.client.api.communication.request.cud.ODataValueUpdateRequest; @@ -367,6 +368,21 @@ public class PrimitiveComplexITCase extends AbstractBaseTestITCase { assertEquals("Test String1", IOUtils.toString((InputStream) value.toValue(), "UTF-8")); } + @Test + public void updatePropertyValueMinimalResponse() throws Exception { + ODataValueUpdateRequest request = getClient().getCUDRequestFactory().getValueUpdateRequest( + getClient().newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESTwoPrim").appendKeySegment(32766) + .appendPropertySegment("PropertyString") + .build(), + UpdateType.REPLACE, + getClient().getObjectFactory().newPrimitiveValueBuilder().buildString("Test String1")); + request.setPrefer(getClient().newPreferences().returnMinimal()); + + final ODataValueUpdateResponse response = request.execute(); + assertEquals(HttpStatusCode.NO_CONTENT.getStatusCode(), response.getStatusCode()); + assertEquals("return=\"minimal\"", response.getHeader(HeaderName.preferenceApplied).iterator().next()); + } + @Test public void readPrimitiveCollectionCount() { final ODataValueRequest request = getClient().getRetrieveRequestFactory() 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 bbd9d51f6..9cd8e3466 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 @@ -25,6 +25,8 @@ 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.header.HeaderName; +import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetRequest; import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; import org.apache.olingo.client.api.domain.ClientEntity; import org.apache.olingo.client.api.domain.ClientEntitySet; @@ -211,7 +213,7 @@ public class SystemQueryOptionITCase extends AbstractBaseTestITCase { // Check initial next link format URI nextLink = response.getBody().getNext(); - assertEquals(SERVICE_URI + "/ESServerSidePaging?%24skiptoken=1", nextLink.toASCIIString()); + assertEquals(SERVICE_URI + "/ESServerSidePaging?%24skiptoken=1%2A10", nextLink.toASCIIString()); // Check subsequent next links response = client.getRetrieveRequestFactory() @@ -219,7 +221,7 @@ public class SystemQueryOptionITCase extends AbstractBaseTestITCase { .execute(); nextLink = response.getBody().getNext(); - assertEquals(SERVICE_URI + "/ESServerSidePaging?%24skiptoken=2", nextLink.toASCIIString()); + assertEquals(SERVICE_URI + "/ESServerSidePaging?%24skiptoken=2%2A10", nextLink.toASCIIString()); } @Test @@ -236,7 +238,7 @@ public class SystemQueryOptionITCase extends AbstractBaseTestITCase { // Check initial next link format URI nextLink = response.getBody().getNext(); - assertEquals(SERVICE_URI + "/ESServerSidePaging?%24count=true&%24skiptoken=1", + assertEquals(SERVICE_URI + "/ESServerSidePaging?%24count=true&%24skiptoken=1%2A10", nextLink.toASCIIString()); int token = 1; @@ -250,7 +252,7 @@ public class SystemQueryOptionITCase extends AbstractBaseTestITCase { nextLink = response.getBody().getNext(); if (nextLink != null) { - assertEquals(SERVICE_URI + "/ESServerSidePaging?%24count=true&%24skiptoken=" + token, + assertEquals(SERVICE_URI + "/ESServerSidePaging?%24count=true&%24skiptoken=" + token + "%2A10", nextLink.toASCIIString()); } } @@ -258,6 +260,19 @@ public class SystemQueryOptionITCase extends AbstractBaseTestITCase { assertEquals(50 + 1, token); } + @Test + public void nextLinkFormatWithClientPageSize() { + final ODataClient client = getClient(); + final URI uri = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment(ES_SERVER_SIDE_PAGING).build(); + ODataEntitySetRequest request = client.getRetrieveRequestFactory().getEntitySetRequest(uri); + request.setPrefer(getClient().newPreferences().maxPageSize(7)); + + final ODataRetrieveResponse response = request.execute(); + assertEquals("odata.maxpagesize=\"7\"", response.getHeader(HeaderName.preferenceApplied).iterator().next()); + assertEquals(SERVICE_URI + '/' + ES_SERVER_SIDE_PAGING + "?%24skiptoken=1%2A" + 7, + response.getBody().getNext().toASCIIString()); + } + @Test public void testNegativeSkip() { ODataClient client = getClient(); diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpHeader.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpHeader.java index ab284b1f6..de804fc44 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpHeader.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpHeader.java @@ -162,12 +162,17 @@ public interface HttpHeader { */ public static final String ODATA_MAX_VERSION = "OData-MaxVersion"; - // CHECKSTYLE:OFF /** - * OData Prefer Header - * See - * {@link OData Version 4.0 Part 1: Protocol Plus Errata 01} + * OData Prefer Header. + * See + * OData Version 4.0 Part 1: Protocol and RFC 7240. */ public static final String PREFER = "Prefer"; - // CHECKSTYLE:ON + + /** + * OData Preference-Applied Header. + * See + * OData Version 4.0 Part 1: Protocol and RFC 7240. + */ + public static final String PREFERENCE_APPLIED = "Preference-Applied"; } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalActionProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalActionProcessor.java index da8c5cc38..b2a9ca421 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalActionProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalActionProcessor.java @@ -40,6 +40,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.DeserializerResult; +import org.apache.olingo.server.api.prefer.Preferences.Return; +import org.apache.olingo.server.api.prefer.PreferencesApplied; import org.apache.olingo.server.api.processor.ActionComplexCollectionProcessor; import org.apache.olingo.server.api.processor.ActionComplexProcessor; import org.apache.olingo.server.api.processor.ActionEntityCollectionProcessor; @@ -58,7 +60,7 @@ import org.apache.olingo.server.tecsvc.data.DataProvider; import org.apache.olingo.server.tecsvc.data.EntityActionResult; /** - * Technical Processor for entity-related functionality. + * Technical Processor for action-related functionality. */ public class TechnicalActionProcessor extends TechnicalProcessor implements ActionEntityCollectionProcessor, ActionEntityProcessor, @@ -92,16 +94,26 @@ public class TechnicalActionProcessor extends TechnicalProcessor throw new ODataApplicationException("The action could not be executed.", HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); } - final EdmEntitySet edmEntitySet = getEdmEntitySet(uriInfo.asUriInfoResource()); - final EdmEntityType type = (EdmEntityType) action.getReturnType().getType(); - final ODataFormat format = ODataFormat.fromContentType(responseFormat); - EntityCollectionSerializerOptions options = EntityCollectionSerializerOptions.with() - .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, type, false)) - .build(); - response.setContent(odata.createSerializer(format) - .entityCollection(serviceMetadata, type, collection, options).getContent()); - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final EdmEntitySet edmEntitySet = getEdmEntitySet(uriInfo.asUriInfoResource()); + final EdmEntityType type = (EdmEntityType) action.getReturnType().getType(); + final ODataFormat format = ODataFormat.fromContentType(responseFormat); + final EntityCollectionSerializerOptions options = EntityCollectionSerializerOptions.with() + .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, type, false)) + .build(); + response.setContent(odata.createSerializer(format) + .entityCollection(serviceMetadata, type, collection, options).getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } } @Override @@ -129,18 +141,32 @@ public class TechnicalActionProcessor extends TechnicalProcessor HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); } } else { - final ODataFormat format = ODataFormat.fromContentType(responseFormat); - response.setContent(odata.createSerializer(format).entity( - serviceMetadata, - type, - entityResult.getEntity(), - EntitySerializerOptions.with() - .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, type, true)) - .build()) - .getContent()); - response.setStatusCode((entityResult.isCreated() ? HttpStatusCode.CREATED : HttpStatusCode.OK) - .getStatusCode()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final ODataFormat format = ODataFormat.fromContentType(responseFormat); + response.setContent(odata.createSerializer(format).entity( + serviceMetadata, + type, + entityResult.getEntity(), + EntitySerializerOptions.with() + .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, type, true)) + .build()) + .getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode((entityResult.isCreated() ? HttpStatusCode.CREATED : HttpStatusCode.OK) + .getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } + if (entityResult.isCreated()) { + response.setHeader(HttpHeader.LOCATION, + request.getRawBaseUri() + '/' + + odata.createUriHelper().buildCanonicalURL(edmEntitySet, entityResult.getEntity())); + } if (entityResult.getEntity().getETag() != null) { response.setHeader(HttpHeader.ETAG, entityResult.getEntity().getETag()); } @@ -170,17 +196,25 @@ public class TechnicalActionProcessor extends TechnicalProcessor throw new ODataApplicationException("The action could not be executed.", HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); } - EdmPrimitiveType type = (EdmPrimitiveType) action.getReturnType().getType(); - ContextURL contextURL = ContextURL.with().type(type).asCollection().build(); - PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextURL).build(); - SerializerResult result = - odata.createSerializer(ODataFormat.fromContentType(responseFormat)) - .primitiveCollection(serviceMetadata, type, property, options); - - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - response.setContent(result.getContent()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final EdmPrimitiveType type = (EdmPrimitiveType) action.getReturnType().getType(); + final ContextURL contextURL = ContextURL.with().type(type).asCollection().build(); + final PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextURL).build(); + final SerializerResult result = + odata.createSerializer(ODataFormat.fromContentType(responseFormat)) + .primitiveCollection(serviceMetadata, type, property, options); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } } @Override @@ -205,15 +239,22 @@ public class TechnicalActionProcessor extends TechnicalProcessor HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); } } else { - ContextURL contextURL = ContextURL.with().type(type).build(); - PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextURL).build(); - - SerializerResult result = odata.createSerializer(ODataFormat.fromContentType(responseFormat)) - .primitive(serviceMetadata, type, property, options); - - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - response.setContent(result.getContent()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final ContextURL contextURL = ContextURL.with().type(type).build(); + final PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextURL).build(); + final SerializerResult result = odata.createSerializer(ODataFormat.fromContentType(responseFormat)) + .primitive(serviceMetadata, type, property, options); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } } } @@ -240,17 +281,25 @@ public class TechnicalActionProcessor extends TechnicalProcessor throw new ODataApplicationException("The action could not be executed.", HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); } - EdmComplexType type = (EdmComplexType) action.getReturnType().getType(); - ContextURL contextURL = ContextURL.with().type(type).asCollection().build(); - ComplexSerializerOptions options = ComplexSerializerOptions.with().contextURL(contextURL).build(); - SerializerResult result = - odata.createSerializer(ODataFormat.fromContentType(responseFormat)).complexCollection(serviceMetadata, type, - property, options); - - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - response.setContent(result.getContent()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final EdmComplexType type = (EdmComplexType) action.getReturnType().getType(); + final ContextURL contextURL = ContextURL.with().type(type).asCollection().build(); + final ComplexSerializerOptions options = ComplexSerializerOptions.with().contextURL(contextURL).build(); + final SerializerResult result = + odata.createSerializer(ODataFormat.fromContentType(responseFormat)).complexCollection(serviceMetadata, type, + property, options); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } } @Override @@ -275,16 +324,23 @@ public class TechnicalActionProcessor extends TechnicalProcessor HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); } } else { - ContextURL contextURL = ContextURL.with().type(type).build(); - ComplexSerializerOptions options = ComplexSerializerOptions.with().contextURL(contextURL).build(); - - SerializerResult result = - odata.createSerializer(ODataFormat.fromContentType(responseFormat)).complex(serviceMetadata, type, property, - options); - - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - response.setContent(result.getContent()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final ContextURL contextURL = ContextURL.with().type(type).build(); + final ComplexSerializerOptions options = ComplexSerializerOptions.with().contextURL(contextURL).build(); + final SerializerResult result = + odata.createSerializer(ODataFormat.fromContentType(responseFormat)).complex(serviceMetadata, type, property, + options); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } } } 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 575b914df..217d7689e 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 @@ -40,6 +40,8 @@ import org.apache.olingo.server.api.ODataResponse; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.DeserializerResult; import org.apache.olingo.server.api.deserializer.ODataDeserializer; +import org.apache.olingo.server.api.prefer.PreferencesApplied; +import org.apache.olingo.server.api.prefer.Preferences.Return; import org.apache.olingo.server.api.processor.CountEntityCollectionProcessor; import org.apache.olingo.server.api.processor.EntityCollectionProcessor; import org.apache.olingo.server.api.processor.EntityProcessor; @@ -162,11 +164,20 @@ public class TechnicalEntityProcessor extends TechnicalProcessor expand = deserializerResult.getExpandTree(); } - final ODataFormat format = ODataFormat.fromContentType(responseFormat); - response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, format, expand, null) - .getContent()); - response.setStatusCode(HttpStatusCode.CREATED.getStatusCode()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final ODataFormat format = ODataFormat.fromContentType(responseFormat); + response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, format, expand, null) + .getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode(HttpStatusCode.CREATED.getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } response.setHeader(HttpHeader.LOCATION, request.getRawBaseUri() + '/' + odata.createUriHelper().buildCanonicalURL(edmEntitySet, entity)); if (entity.getETag() != null) { @@ -209,11 +220,20 @@ public class TechnicalEntityProcessor extends TechnicalProcessor dataProvider.update(request.getRawBaseUri(), edmEntitySet, entity, changedEntity, request.getMethod() == HttpMethod.PATCH, false); - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - final ODataFormat format = ODataFormat.fromContentType(responseFormat); - response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, format, null, null) - .getContent()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + final ODataFormat format = ODataFormat.fromContentType(responseFormat); + response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, format, null, null) + .getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } if (entity.getETag() != null) { response.setHeader(HttpHeader.ETAG, entity.getETag()); } @@ -234,11 +254,20 @@ public class TechnicalEntityProcessor extends TechnicalProcessor dataProvider.setMedia(entity, odata.createFixedFormatDeserializer().binary(request.getBody()), requestFormat.toContentTypeString()); - final ODataFormat format = ODataFormat.fromContentType(responseFormat); - response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, format, null, null) - .getContent()); - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final ODataFormat format = ODataFormat.fromContentType(responseFormat); + response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, format, null, null) + .getContent()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); + } if (entity.getETag() != null) { response.setHeader(HttpHeader.ETAG, entity.getETag()); } @@ -414,10 +443,12 @@ public class TechnicalEntityProcessor extends TechnicalProcessor SkipHandler.applySkipSystemQueryHandler(uriInfo.getSkipOption(), entitySet); TopHandler.applyTopSystemQueryOption(uriInfo.getTopOption(), entitySet); - ServerSidePagingHandler.applyServerSidePaging(uriInfo.getSkipTokenOption(), + final Integer pageSize = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getMaxPageSize(); + final Integer serverPageSize = ServerSidePagingHandler.applyServerSidePaging(uriInfo.getSkipTokenOption(), entitySet, edmEntitySet, - request.getRawRequestUri()); + request.getRawRequestUri(), + pageSize); // Apply expand system query option final ODataFormat format = ODataFormat.fromContentType(requestedContentType); @@ -443,6 +474,10 @@ public class TechnicalEntityProcessor extends TechnicalProcessor response.setContent(serializerResult.getContent()); response.setStatusCode(HttpStatusCode.OK.getStatusCode()); response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString()); + if (pageSize != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().maxPageSize(serverPageSize).build().toString()); + } } private SerializerResult serializeEntityCollection(final EntityCollection entityCollection, 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 1c0d04928..6a9af47ec 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 @@ -48,6 +48,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.processor.ComplexCollectionProcessor; import org.apache.olingo.server.api.processor.ComplexProcessor; import org.apache.olingo.server.api.processor.CountComplexCollectionProcessor; @@ -305,17 +307,26 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor dataProvider.updateETag(entity); - response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - if (representationType == RepresentationType.VALUE) { - response.setContent( - serializePrimitiveValue(property, edmProperty, (EdmPrimitiveType) edmProperty.getType(), null)); + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + if (representationType == RepresentationType.VALUE) { + response.setContent( + serializePrimitiveValue(property, edmProperty, (EdmPrimitiveType) edmProperty.getType(), null)); + } else { + final ODataFormat format = ODataFormat.fromContentType(responseFormat); + final SerializerResult result = serializeProperty(entity, edmEntitySet, path, property, edmProperty, + edmProperty.getType(), null, representationType, format, null, null); + response.setContent(result.getContent()); + } + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); } else { - final ODataFormat format = ODataFormat.fromContentType(responseFormat); - final SerializerResult result = serializeProperty(entity, edmEntitySet, path, property, edmProperty, - edmProperty.getType(), null, representationType, format, null, null); - response.setContent(result.getContent()); + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toString()); } - response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); if (entity.getETag() != null) { response.setHeader(HttpHeader.ETAG, entity.getETag()); } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/ServerSidePagingHandler.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/ServerSidePagingHandler.java index 9c6b68849..71496fd64 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/ServerSidePagingHandler.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/queryoptions/options/ServerSidePagingHandler.java @@ -34,68 +34,63 @@ public class ServerSidePagingHandler { private static final int MAX_PAGE_SIZE = 10; private static final String ES_SERVER_SIDE_PAGING = "ESServerSidePaging"; - public static void applyServerSidePaging(final SkipTokenOption skipTokenOption, final EntityCollection entitySet, - final EdmEntitySet edmEntitySet, final String rawRequestUri) throws ODataApplicationException { + /** + *

Applies server-side paging to the given entity collection.

+ *

The next link is constructed and set in the data. It must support client-specified + * page sizes. Therefore, the format page*pageSize (with a literal asterisk) + * has been chosen for the skiptoken.

+ * @param skipTokenOption the current skiptoken option (from a previous response's next link) + * @param entityCollection the data + * @param edmEntitySet the EDM entity set to decide whether paging must be done + * @param rawRequestUri the request URI (used to construct the next link) + * @param preferredPageSize the client's preference for page size + * @return the chosen page size (or null if no paging has been done); + * could be used in the Preference-Applied HTTP header + * @throws ODataApplicationException + */ + public static Integer applyServerSidePaging(final SkipTokenOption skipTokenOption, EntityCollection entityCollection, + final EdmEntitySet edmEntitySet, final String rawRequestUri, final Integer preferredPageSize) + throws ODataApplicationException { if (edmEntitySet != null && shouldApplyServerSidePaging(edmEntitySet)) { - final int maxPageSize = getMaxPageSize(); + final int pageSize = getPageSize(getPageSize(skipTokenOption), preferredPageSize); final int page = getPage(skipTokenOption); - final int itemsToSkip = maxPageSize * page; + final int itemsToSkip = pageSize * page; - if (itemsToSkip <= entitySet.getEntities().size()) { - SkipHandler.popAtMost(entitySet, itemsToSkip); - final int remainingItems = entitySet.getEntities().size(); - TopHandler.reduceToSize(entitySet, maxPageSize); + if (itemsToSkip <= entityCollection.getEntities().size()) { + SkipHandler.popAtMost(entityCollection, itemsToSkip); + final int remainingItems = entityCollection.getEntities().size(); + TopHandler.reduceToSize(entityCollection, pageSize); - // Determine if a new next Link has to be provided - if (remainingItems > maxPageSize) { - entitySet.setNext(createNextLink(rawRequestUri, page + 1)); + // Determine if a new next Link has to be provided. + if (remainingItems > pageSize) { + entityCollection.setNext(createNextLink(rawRequestUri, page + 1, pageSize)); } } else { throw new ODataApplicationException("Nothing found.", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT); } + return pageSize; } + return null; } - private static URI createNextLink(final String rawRequestUri, final Integer page) + private static URI createNextLink(final String rawRequestUri, final Integer page, final int pageSize) throws ODataApplicationException { + // Remove a maybe existing skiptoken, making sure that the query part is not empty. + String nextlink = rawRequestUri.contains("?") ? + rawRequestUri.replaceAll("(\\$|%24)skiptoken=.+&?", "").replaceAll("(\\?|&)$", "") : + rawRequestUri; + // Add a question mark or an ampersand, depending on the current query part. + nextlink += nextlink.contains("?") ? '&' : '?'; + + // Append the new skiptoken. try { - // Remove skip token - String nextlink = rawRequestUri; - - // Remove a may existing skiptoken, make sure that the query part is not empty - if (rawRequestUri.contains("?")) { - nextlink = rawRequestUri.replaceAll("(\\$|%24)skiptoken=.+&?", "").replaceAll("(\\?|&)$", ""); - } - - // Add a question mark or an ampersand, depending of the current query part - if (!nextlink.contains("?")) { - nextlink = nextlink + "?"; - } else { - nextlink = nextlink + "&"; - } - - // Append the new nextlink - return new URI(nextlink + Encoder.encode(SystemQueryOptionKind.SKIPTOKEN.toString()) + "=" - + Encoder.encode(page.toString())); - - } catch (URISyntaxException e) { + return new URI(nextlink + Encoder.encode(SystemQueryOptionKind.SKIPTOKEN.toString()) + '=' + + Encoder.encode(page.toString() + '*' + pageSize)); + } catch (final URISyntaxException e) { throw new ODataApplicationException("Exception while constructing next link", - HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); - } - } - - private static int getPage(final SkipTokenOption skipTokenOption) throws ODataApplicationException { - if (skipTokenOption != null) { - try { - return Integer.parseInt(skipTokenOption.getValue()); - } catch (NumberFormatException e) { - throw new ODataApplicationException("Invalid skip token", HttpStatusCode.BAD_REQUEST.getStatusCode(), - Locale.ROOT); - } - } else { - return 0; + HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT, e); } } @@ -103,8 +98,40 @@ public class ServerSidePagingHandler { return ES_SERVER_SIDE_PAGING.equals(edmEntitySet.getName()); } - private static int getMaxPageSize() { - // TODO Consider odata.maxpagesize preference? - return MAX_PAGE_SIZE; + private static int getPageSize(final int skipTokenPageSize, final Integer preferredPageSize) { + return skipTokenPageSize > 0 ? skipTokenPageSize : + preferredPageSize == null || preferredPageSize >= MAX_PAGE_SIZE ? + MAX_PAGE_SIZE : + preferredPageSize; + } + + private static int getPageSize(final SkipTokenOption skipTokenOption) throws ODataApplicationException { + if (skipTokenOption != null && skipTokenOption.getValue().length() >= 3 + && skipTokenOption.getValue().contains("*")) { + final String value = skipTokenOption.getValue(); + try { + return Integer.parseInt(value.substring(value.indexOf('*') + 1)); + } catch (final NumberFormatException e) { + throw new ODataApplicationException("Invalid skip token", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT, e); + } + } else { + return 0; + } + } + + private static int getPage(final SkipTokenOption skipTokenOption) throws ODataApplicationException { + if (skipTokenOption != null && skipTokenOption.getValue().length() >= 3 + && skipTokenOption.getValue().contains("*")) { + final String value = skipTokenOption.getValue(); + try { + return Integer.parseInt(value.substring(0, value.indexOf('*'))); + } catch (final NumberFormatException e) { + throw new ODataApplicationException("Invalid skip token", HttpStatusCode.BAD_REQUEST.getStatusCode(), + Locale.ROOT, e); + } + } else { + return 0; + } } }