From 0d015cb7b6e89ac32573fd8021e142689f82beef Mon Sep 17 00:00:00 2001 From: Ramesh Reddy Date: Fri, 24 Apr 2015 17:58:00 -0500 Subject: [PATCH 1/4] OLINGO-573: fixing the rollback logic in the case of batch changeset error --- .../olingo/server/core/ServiceHandler.java | 12 +++++++--- .../server/core/requests/BatchRequest.java | 23 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceHandler.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceHandler.java index 621f0debd..2e84b400b 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceHandler.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceHandler.java @@ -235,21 +235,27 @@ public interface ServiceHandler extends Processor { * During a batch operation, this method starts the transaction (if any) before any operation is handled * by the service. No nested transactions. * @return must return a unique transaction id that references a atomic operation. + * @throws ODataTranslatedException + * @throws ODataApplicationException */ - String startTransaction(); + String startTransaction() throws ODataTranslatedException, ODataApplicationException;; /** * When a batch operation is complete and all the intermediate service requests are successful, then * commit is called with transaction id returned in the startTransaction method. * @param txnId + * @throws ODataTranslatedException + * @throws ODataApplicationException */ - void commit(String txnId); + void commit(String txnId) throws ODataTranslatedException, ODataApplicationException;; /** * When a batch operation is in-complete due to an error in the middle of changeset, then rollback is * called with transaction id, that returned from startTransaction method. * @param txnId + * @throws ODataTranslatedException + * @throws ODataApplicationException */ - void rollback(String txnId); + void rollback(String txnId) throws ODataTranslatedException, ODataApplicationException;; /** * This is not complete, more URL parsing changes required. Cross join between two entities. diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/BatchRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/BatchRequest.java index 25af023e0..0547775c8 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/BatchRequest.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/BatchRequest.java @@ -77,12 +77,25 @@ public class BatchRequest extends ServiceRequest { for (BatchRequestPart part : parts) { if (part.isChangeSet()) { - String txnId = handler.startTransaction(); - partResponse = processChangeSet(part, handler); - if (partResponse.getResponses().get(0).getStatusCode() > 400) { - handler.rollback(txnId); + String txnId = null; + try { + txnId = handler.startTransaction(); + partResponse = processChangeSet(part, handler); + if (partResponse.getResponses().get(0).getStatusCode() > 400) { + handler.rollback(txnId); + } + handler.commit(txnId); + } catch(ODataTranslatedException e) { + if (txnId != null) { + handler.rollback(txnId); + } + throw e; + } catch (ODataApplicationException e) { + if (txnId != null) { + handler.rollback(txnId); + } + throw e; } - handler.commit(txnId); } else { // single request, a static request ODataRequest partRequest = part.getRequests().get(0); From ae97061e3cb12a4683ae40b34012ffe2e6c93d16 Mon Sep 17 00:00:00 2001 From: Christian Amend Date: Mon, 27 Apr 2015 14:23:02 +0200 Subject: [PATCH 2/4] [OLINGO-604] Implement Action imports in TechSvc part 2 --- .../fit/tecsvc/client/ActionImportITCase.java | 296 ++++++++++++++++++ .../olingo/client/api/uri/SegmentType.java | 2 + .../olingo/client/api/uri/URIBuilder.java | 8 + .../client/core/uri/URIBuilderImpl.java | 11 +- .../client/core/uri/v4/URIBuilderTest.java | 4 +- .../olingo/commons/api/data/Entity.java | 1 + .../olingo/server/tecsvc/data/ActionData.java | 124 +++++--- .../server/tecsvc/data/DataCreator.java | 14 +- .../server/tecsvc/data/DataProvider.java | 20 ++ .../tecsvc/data/EntityActionResult.java | 43 +++ .../processor/TechnicalEntityProcessor.java | 117 +++++-- .../TechnicalPrimitiveComplexProcessor.java | 76 +++-- .../tecsvc/processor/TechnicalProcessor.java | 15 + 13 files changed, 620 insertions(+), 111 deletions(-) create mode 100644 fit/src/test/java/org/apache/olingo/fit/tecsvc/client/ActionImportITCase.java create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/EntityActionResult.java 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 new file mode 100644 index 000000000..49502bd66 --- /dev/null +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/ActionImportITCase.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.fit.tecsvc.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.math.BigDecimal; +import java.net.URI; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +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.response.ODataInvokeResponse; +import org.apache.olingo.client.core.ODataClientFactory; +import org.apache.olingo.commons.api.domain.ODataCollectionValue; +import org.apache.olingo.commons.api.domain.ODataComplexValue; +import org.apache.olingo.commons.api.domain.ODataEntity; +import org.apache.olingo.commons.api.domain.ODataEntitySet; +import org.apache.olingo.commons.api.domain.ODataProperty; +import org.apache.olingo.commons.api.domain.ODataValue; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.fit.AbstractBaseTestITCase; +import org.apache.olingo.fit.tecsvc.TecSvcConst; +import org.junit.Test; + +public class ActionImportITCase extends AbstractBaseTestITCase { + + @Test + public void primitveAction() throws Exception { + URI actionURI = + getClient().newURIBuilder(TecSvcConst.BASE_URI).appendActionCallSegment("AIRTString").build(); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataProperty.class).execute(); + assertEquals(200, 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(); + try { + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataProperty.class, parameters) + .execute(); + fail("Expected an ODataClientErrorException"); + } catch (ODataClientErrorException e) { + assertEquals(400, e.getStatusLine().getStatusCode()); + } + } + + @Test + public void primitveCollectionAction() throws Exception { + 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()); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataProperty.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataCollectionValue valueArray = response.getBody().getCollectionValue(); + assertEquals(3, valueArray.size()); + Iterator iterator = valueArray.iterator(); + assertEquals("PT1S", iterator.next().asPrimitive().toValue()); + assertEquals("PT2S", iterator.next().asPrimitive().toValue()); + assertEquals("PT3S", iterator.next().asPrimitive().toValue()); + } + + @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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataProperty.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataComplexValue complexValue = response.getBody().getComplexValue(); + ODataProperty propInt16 = complexValue.get("PropertyInt16"); + assertNotNull(propInt16); + assertEquals(3, propInt16.getPrimitiveValue().toValue()); + ODataProperty propString = complexValue.get("PropertyString"); + assertNotNull(propString); + assertEquals("UARTCTTwoPrimParam string value", propString.getPrimitiveValue().toValue()); + } + + @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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataProperty.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataCollectionValue 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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataProperty.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataCollectionValue complexValueCollection = response.getBody().getCollectionValue(); + assertEquals(1, complexValueCollection.size()); + Iterator iterator = complexValueCollection.iterator(); + + ODataComplexValue next = iterator.next().asComplex(); + assertEquals(16, next.get("PropertyInt16").getPrimitiveValue().toValue()); + assertEquals("Test123", next.get("PropertyString").getPrimitiveValue().toValue()); + } + + @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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataProperty.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataCollectionValue complexValueCollection = response.getBody().getCollectionValue(); + assertEquals(3, complexValueCollection.size()); + Iterator iterator = complexValueCollection.iterator(); + + ODataComplexValue next = iterator.next().asComplex(); + assertEquals(16, next.get("PropertyInt16").getPrimitiveValue().toValue()); + assertEquals("Test123", next.get("PropertyString").getPrimitiveValue().toValue()); + + next = iterator.next().asComplex(); + assertEquals(17, next.get("PropertyInt16").getPrimitiveValue().toValue()); + assertEquals("Test456", next.get("PropertyString").getPrimitiveValue().toValue()); + + next = iterator.next().asComplex(); + assertEquals(18, next.get("PropertyInt16").getPrimitiveValue().toValue()); + assertEquals("Test678", next.get("PropertyString").getPrimitiveValue().toValue()); + } + + @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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataEntity.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataEntity entity = response.getBody(); + ODataProperty propInt16 = entity.getProperty("PropertyInt16"); + assertNotNull(propInt16); + assertEquals(-365, propInt16.getPrimitiveValue().toValue()); + ODataProperty propString = entity.getProperty("PropertyString"); + assertNotNull(propString); + assertEquals("Test String2", propString.getPrimitiveValue().toValue()); + } + + @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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataEntitySet.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataEntitySet entitySet = response.getBody(); + assertEquals(3, entitySet.getEntities().size()); + Integer key = 1; + for (ODataEntity entity : entitySet.getEntities()) { + assertEquals(key, entity.getProperty("PropertyInt16").getPrimitiveValue().toValue()); + key++; + } + } + + @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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataEntitySet.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataEntitySet 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)); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataEntitySet.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataEntitySet entitySet = response.getBody(); + assertEquals(0, entitySet.getEntities().size()); + } + + @Test + public void entityCollectionActionETAllPrim() throws Exception { + 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()); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataEntitySet.class, parameters) + .execute(); + assertEquals(200, response.getStatusCode()); + ODataEntitySet entitySet = response.getBody(); + assertEquals(3, entitySet.getEntities().size()); + Integer key = 1; + for (ODataEntity entity : entitySet.getEntities()) { + assertEquals(key, entity.getProperty("PropertyInt16").getPrimitiveValue().toValue()); + key++; + } + } + + @Test + public void entityActionETAllPrim() throws Exception { + 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()); + ODataInvokeResponse response = + getClient().getInvokeRequestFactory().getActionInvokeRequest(actionURI, ODataEntity.class, parameters) + .execute(); + // Check 201 + assertEquals(201, response.getStatusCode()); + } + + @Override + protected ODataClient getClient() { + ODataClient odata = ODataClientFactory.getClient(); + odata.getConfiguration().setDefaultPubFormat(ODataFormat.JSON_NO_METADATA); + return odata; + } + +} diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java index 5da7f09d2..f3f2a5035 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java @@ -37,6 +37,8 @@ public enum SegmentType { COUNT("$count"), BOUND_OPERATION, UNBOUND_OPERATION, + BOUND_ACTION, + UNBOUND_ACTION, METADATA("$metadata"), BATCH("$batch"), LINKS("$links"), diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/URIBuilder.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/URIBuilder.java index af6beb75b..1c8f89d1e 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/URIBuilder.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/URIBuilder.java @@ -355,4 +355,12 @@ public interface URIBuilder { * @see org.apache.olingo.client.api.uri.QueryOption#SELECT */ URIBuilder expandWithSelect(String expandItem, String... selectItems); + + /** + * Appends action segment to the URI. + * + * @param action Action name + * @return current URIBuilder instance + */ + URIBuilder appendActionCallSegment(String action); } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIBuilderImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIBuilderImpl.java index 452596c41..82f090e7a 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIBuilderImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIBuilderImpl.java @@ -179,6 +179,13 @@ public class URIBuilderImpl implements URIBuilder { return this; } + @Override + public URIBuilder appendActionCallSegment(final String action) { + segments.add(new Segment( + segments.size() == 1 ? SegmentType.UNBOUND_ACTION : SegmentType.BOUND_ACTION, action)); + return this; + } + @Override public URIBuilder appendOperationCallSegment(final String operation) { segments.add(new Segment( @@ -266,7 +273,9 @@ public class URIBuilderImpl implements URIBuilder { case BOUND_OPERATION: segmentsBuilder.append(getBoundOperationSeparator()); break; - + case BOUND_ACTION: + segmentsBuilder.append(getBoundOperationSeparator()); + break; default: if (segmentsBuilder.length() > 0 && segmentsBuilder.charAt(segmentsBuilder.length() - 1) != '/') { segmentsBuilder.append('/'); diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java index f55e37f2a..14fa56631 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java @@ -100,10 +100,10 @@ public class URIBuilderTest extends AbstractTest { final URIBuilder uriBuilder = getClient().newURIBuilder(SERVICE_ROOT). appendEntitySetSegment("Categories").appendKeySegment(1). appendNavigationSegment("Products").appendNavigationSegment("Model"). - appendOperationCallSegment("AllOrders"); + appendActionCallSegment("AllOrders"); assertEquals(new org.apache.http.client.utils.URIBuilder( - SERVICE_ROOT + "/Categories(1)/Products/Model.AllOrders()").build(), uriBuilder.build()); + SERVICE_ROOT + "/Categories(1)/Products/Model.AllOrders").build(), uriBuilder.build()); } @Test diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java index acd30225a..167c5c312 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java @@ -158,6 +158,7 @@ public class Entity extends Linked { for (Property property : properties) { if (name.equals(property.getName())) { result = property; + break; } } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java index d2a852529..17561a8fb 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java @@ -24,6 +24,7 @@ import java.util.Calendar; import java.util.List; import java.util.Map; +import org.apache.olingo.commons.api.data.ComplexValue; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Parameter; @@ -49,11 +50,17 @@ public class ActionData { protected static Property primitiveCollectionAction(String name, Map parameters) throws DataProviderException { if ("UARTCollStringTwoParam".equals(name)) { - List collectionValues = new ArrayList(); - int loopCount = (Integer) parameters.get("ParameterInt16").asPrimitive(); + Parameter paramInt16 = parameters.get("ParameterInt16"); + Parameter paramDuration = parameters.get("ParameterDuration"); + if (paramInt16 == null || paramDuration == null) { + throw new DataProviderException("Missing parameters for action: UARTCollStringTwoParam", + HttpStatusCode.BAD_REQUEST); + } + short loopCount = (Short) paramInt16.asPrimitive(); + BigDecimal duration = (BigDecimal) paramDuration.asPrimitive(); EdmPrimitiveType primDuration = OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Duration); - BigDecimal duration = (BigDecimal) parameters.get("ParameterDuration").asPrimitive(); BigDecimal addValue = new BigDecimal(1); + List collectionValues = new ArrayList(); for (int i = 0; i < loopCount; i++) { try { String value = primDuration.valueToString(duration, false, null, null, null, null); @@ -63,78 +70,92 @@ public class ActionData { } duration = duration.add(addValue); } - return DataCreator.createPrimitiveCollection(null, collectionValues); + return new Property(null, name, ValueType.COLLECTION_PRIMITIVE, collectionValues); } throw new DataProviderException("Action " + name + " is not yet implemented."); } protected static Property complexAction(String name, Map parameters) throws DataProviderException { if ("UARTCTTwoPrimParam".equals(name)) { - Integer number = (Integer) parameters.get("ParameterInt16").asPrimitive(); - if (number == null) { - number = new Integer(32767); + Parameter paramInt16 = parameters.get("ParameterInt16"); + Short number; + if (paramInt16 == null) { + number = new Short((short) 32767); + } else { + number = (Short) paramInt16.asPrimitive(); } - Property complexProp = createCTTwoPrimComplexProperty(number, "UARTCTTwoPrimParam string value"); - return complexProp; + return createCTTwoPrimComplexProperty(number, "UARTCTTwoPrimParam string value"); } throw new DataProviderException("Action " + name + " is not yet implemented."); } - private static Property createCTTwoPrimComplexProperty(Integer number, String text) { - List props = new ArrayList(); + private static Property createCTTwoPrimComplexProperty(Short number, String text) { + ComplexValue compValue = new ComplexValue(); Property propInt = new Property(); propInt.setName("PropertyInt16"); propInt.setValue(ValueType.PRIMITIVE, number); - props.add(propInt); + compValue.getValue().add(propInt); Property propString = new Property(); propString.setName("PropertyString"); propString.setValue(ValueType.PRIMITIVE, text); - props.add(propString); + compValue.getValue().add(propString); Property complexProp = new Property(); - complexProp.setValue(ValueType.COMPLEX, props); + complexProp.setValue(ValueType.COMPLEX, compValue); return complexProp; } protected static Property complexCollectionAction(String name, Map parameters) throws DataProviderException { if ("UARTCollCTTwoPrimParam".equals(name)) { - ArrayList complexCollection = new ArrayList(); - complexCollection.add(createCTTwoPrimComplexProperty(16, "Test123")); - complexCollection.add(createCTTwoPrimComplexProperty(17, "Test456")); - complexCollection.add(createCTTwoPrimComplexProperty(18, "Test678")); + List complexCollection = new ArrayList(); + complexCollection.add(createCTTwoPrimComplexProperty((short) 16, "Test123").asComplex()); + complexCollection.add(createCTTwoPrimComplexProperty((short) 17, "Test456").asComplex()); + complexCollection.add(createCTTwoPrimComplexProperty((short) 18, "Test678").asComplex()); - Integer number = (Integer) parameters.get("ParameterInt16").asPrimitive(); - if (number != null && number >= 0 && number < complexCollection.size()) { - complexCollection.subList(number, complexCollection.size() - 1).clear(); + Parameter paramInt16 = parameters.get("ParameterInt16"); + if (paramInt16 != null) { + Short number = (Short) paramInt16.asPrimitive(); + if (number < 0) { + complexCollection.clear(); + } else if (number >= 0 && number < complexCollection.size()) { + complexCollection = complexCollection.subList(0, number); + + } + Property complexCollProperty = new Property(); + complexCollProperty.setValue(ValueType.COLLECTION_COMPLEX, complexCollection); + return complexCollProperty; } - - Property complexCollProperty = new Property(); - complexCollProperty.setValue(ValueType.COLLECTION_COMPLEX, complexCollection); - return complexCollProperty; } throw new DataProviderException("Action " + name + " is not yet implemented."); } - protected static Entity entityAction(String name, Map parameters) throws DataProviderException { + protected static EntityActionResult entityAction(String name, Map parameters) + throws DataProviderException { if ("UARTETTwoKeyTwoPrimParam".equals(name)) { - Integer number = (Integer) parameters.get("ParameterInt16").asPrimitive(); - if (number == null) { - number = 0; + Parameter parameter = parameters.get("ParameterInt16"); + Short number; + if (parameter != null) { + number = (Short) parameter.asPrimitive(); + } else { + number = (short) 0; } + EntityCollection entityCollection = new DataCreator().getData().get("ESTwoKeyTwoPrim"); for (Entity entity : entityCollection.getEntities()) { - if (number.equals(entity.getProperty("PropertyInt16").asPrimitive())) { - return entity; + Object asPrimitive = entity.getProperty("PropertyInt16").asPrimitive(); + if (number.equals(asPrimitive)) { + return new EntityActionResult().setEntity(entity); } } // Entity Not found throw new DataProviderException("Entity not found with key: " + number, HttpStatusCode.NOT_FOUND); } else if ("UARTETAllPrimParam".equals(name)) { - Calendar date = (Calendar) parameters.get("ParameterDate").asPrimitive(); + Parameter paramDate = parameters.get("ParameterDate"); EntityCollection entityCollection = new DataCreator().getData().get("ESAllPrim"); - if (date != null) { + if (paramDate != null) { + Calendar date = (Calendar) paramDate.asPrimitive(); boolean freeKey; Short key = 0; do { @@ -147,10 +168,10 @@ public class ActionData { } key++; } while (!freeKey); - // TODO: Set create response code - return createAllPrimEntity(key, "UARTETAllPrimParam string value", date); + return new EntityActionResult().setEntity(createAllPrimEntity(key, "UARTETAllPrimParam string value", date)) + .setCreated(true); } else { - return entityCollection.getEntities().get(0); + return new EntityActionResult().setEntity(entityCollection.getEntities().get(0)); } } throw new DataProviderException("Action " + name + " is not yet implemented."); @@ -178,26 +199,31 @@ public class ActionData { protected static EntityCollection entityCollectionAction(String name, Map parameters) throws DataProviderException { if ("UARTCollETKeyNavParam".equals(name)) { - Short number = (Short) parameters.get("ParameterInt16").asPrimitive(); - EntityCollection collection = new EntityCollection(); - if (number != null && number > 0) { - for (int i = 1; i <= number; i++) { - collection.getEntities().add(createETKeyNavEntity(number)); - } + Parameter paramInt16 = parameters.get("ParameterInt16"); + Short number; + if (paramInt16 == null) { + number = (short) 0; } else { - return collection; + number = (Short) paramInt16.asPrimitive(); } - } else if ("UARTCollETAllPrimParam".equals(name)) { - Calendar timeOfDay = (Calendar) parameters.get("ParameterTimeOfDay").asPrimitive(); EntityCollection collection = new EntityCollection(); - if (timeOfDay != null) { + if (number > 0) { + for (short i = 1; i <= number; i++) { + collection.getEntities().add(createETKeyNavEntity(i)); + } + } + return collection; + } else if ("UARTCollETAllPrimParam".equals(name)) { + Parameter paramTimeOfDay = parameters.get("ParameterTimeOfDay"); + EntityCollection collection = new EntityCollection(); + if (paramTimeOfDay != null) { + Calendar timeOfDay = (Calendar) paramTimeOfDay.asPrimitive(); int count = timeOfDay.get(Calendar.HOUR_OF_DAY); for (short i = 1; i <= count; i++) { collection.getEntities().add(createAllPrimEntity(i, "UARTCollETAllPrimParam int16 value: " + i, null)); } - } else { - return collection; - } + } + return collection; } throw new DataProviderException("Action " + name + " is not yet implemented."); } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java index 37b775249..dcb3bfd9b 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java @@ -85,13 +85,13 @@ public class DataCreator { private EntityCollection createESTwoKeyTwoPrim() { EntityCollection entitySet = new EntityCollection(); - entitySet.getEntities().add(createETTwoKeyTwoPrimEntity(32767, "Test String1")); - entitySet.getEntities().add(createETKeyNavEntity(-365, "Test String2")); - entitySet.getEntities().add(createETKeyNavEntity(-32766, "Test String3")); + entitySet.getEntities().add(createETTwoKeyTwoPrimEntity((short) 32767, "Test String1")); + entitySet.getEntities().add(createETTwoKeyTwoPrimEntity((short) -365, "Test String2")); + entitySet.getEntities().add(createETTwoKeyTwoPrimEntity((short) -32766, "Test String3")); return entitySet; } - private Entity createETTwoKeyTwoPrimEntity(int propertyInt16, String propertyString) { + private Entity createETTwoKeyTwoPrimEntity(short propertyInt16, String propertyString) { return new Entity().addProperty(createPrimitive("PropertyInt16", propertyInt16)) .addProperty(createPrimitive("PropertyString", propertyString)); } @@ -266,7 +266,7 @@ public class DataCreator { .addProperty(createPrimitive("PropertyGuid", UUID.fromString("76543201-23ab-cdef-0123-456789dddfff"))) .addProperty(createPrimitive("PropertyTimeOfDay", getTime(23, 49, 14)))); - entitySet.getEntities().add(new Entity().addProperty(createPrimitive("PropertyInt16", 0)) + entitySet.getEntities().add(new Entity().addProperty(createPrimitive("PropertyInt16", (short) 0)) .addProperty(createPrimitive("PropertyString", "")).addProperty(createPrimitive("PropertyBoolean", false)) .addProperty(createPrimitive("PropertyByte", 0)).addProperty(createPrimitive("PropertySByte", 0)) .addProperty(createPrimitive("PropertyInt32", 0)).addProperty(createPrimitive("PropertyInt64", 0)) @@ -600,7 +600,7 @@ public class DataCreator { return new Property(null, name, ValueType.COLLECTION_COMPLEX, complexCollection); } - private static Calendar getDateTime(final int year, final int month, final int day, + protected static Calendar getDateTime(final int year, final int month, final int day, final int hour, final int minute, final int second) { Calendar dateTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")); dateTime.clear(); @@ -608,7 +608,7 @@ public class DataCreator { return dateTime; } - private static Calendar getTime(final int hour, final int minute, final int second) { + protected static Calendar getTime(final int hour, final int minute, final int second) { Calendar time = Calendar.getInstance(TimeZone.getTimeZone("GMT")); time.clear(); time.set(Calendar.HOUR_OF_DAY, hour); diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java index 878efce76..676b6ce26 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java @@ -495,11 +495,31 @@ public class DataProvider { return ActionData.primitiveAction(name, actionParameters); } + public Property processActionComplex(String name, Map actionParameters) + throws DataProviderException { + return ActionData.complexAction(name, actionParameters); + } + + public Property processActionComplexCollection(String name, Map actionParameters) + throws DataProviderException { + return ActionData.complexCollectionAction(name, actionParameters); + } + public Property processActionPrimitiveCollection(String name, Map actionParameters) throws DataProviderException { return ActionData.primitiveCollectionAction(name, actionParameters); } + public EntityActionResult processActionEntity(String name, Map actionParameters) + throws DataProviderException { + return ActionData.entityAction(name, actionParameters); + } + + public EntityCollection processActionEntityCollection(String name, Map actionParameters) + throws DataProviderException { + return ActionData.entityCollectionAction(name, actionParameters); + } + public void setEdm(final Edm edm) { this.edm = edm; } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/EntityActionResult.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/EntityActionResult.java new file mode 100644 index 000000000..8611cf78a --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/EntityActionResult.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.tecsvc.data; + +import org.apache.olingo.commons.api.data.Entity; + +public class EntityActionResult { + private Entity entity; + private boolean created = false; + + public Entity getEntity() { + return entity; + } + public EntityActionResult setEntity(Entity entity) { + this.entity = entity; + return this; + } + public boolean isCreated() { + return created; + } + public EntityActionResult setCreated(boolean created) { + this.created = created; + return this; + } + + +} 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 6d7d92817..024628caa 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 @@ -6,9 +6,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -52,6 +52,7 @@ import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions import org.apache.olingo.server.api.serializer.EntitySerializerOptions; import org.apache.olingo.server.api.serializer.ODataSerializer; import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerResult; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriResourceAction; import org.apache.olingo.server.api.uri.UriResourceEntitySet; @@ -59,6 +60,7 @@ import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.SelectOption; import org.apache.olingo.server.tecsvc.data.DataProvider; +import org.apache.olingo.server.tecsvc.data.EntityActionResult; import org.apache.olingo.server.tecsvc.data.RequestValidator; import org.apache.olingo.server.tecsvc.processor.queryoptions.ExpandSystemQueryOptionHandler; import org.apache.olingo.server.tecsvc.processor.queryoptions.options.CountHandler; @@ -117,14 +119,14 @@ public class TechnicalEntityProcessor extends TechnicalProcessor ODataSerializer serializer = odata.createSerializer(format); final ExpandOption expand = uriInfo.getExpandOption(); final SelectOption select = uriInfo.getSelectOption(); - + // Transform the entity graph to a tree. The construction is controlled by the expand tree. - // Apply all expand system query options to the tree.So the expanded navigation properties can be modified + // Apply all expand system query options to the tree.So the expanded navigation properties can be modified // for serialization,without affecting the data stored in the database. final ExpandSystemQueryOptionHandler expandHandler = new ExpandSystemQueryOptionHandler(); - final EntityCollection entitySetSerialization = expandHandler.transformEntitySetGraphToTree(entitySet, - edmEntitySet, - expand); + final EntityCollection entitySetSerialization = expandHandler.transformEntitySetGraphToTree(entitySet, + edmEntitySet, + expand); expandHandler.applyExpandQueryOptions(entitySetSerialization, edmEntitySet, expand); // Serialize @@ -147,8 +149,32 @@ public class TechnicalEntityProcessor extends TechnicalProcessor public void processActionEntityCollection(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo, final ContentType requestFormat, final ContentType responseFormat) throws ODataApplicationException, DeserializerException, SerializerException { - throw new ODataApplicationException("Process entity collection is not supported yet.", - HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + EdmAction action = checkBoundAndExtractAction(uriInfo); + DeserializerResult deserializerResult = + odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) + .actionParameters(request.getBody(), action); + + EntityCollection collection = + dataProvider.processActionEntityCollection(action.getName(), deserializerResult.getActionParameters()); + + if (collection == null) { + // Collection Propertys must never be null + throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); + } else if (collection.getEntities().contains(null) && !action.getReturnType().isNullable()) { + // Not nullable return type but array contains a null value + throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); + } + EdmEntityType type = (EdmEntityType) action.getReturnType().getType(); + ContextURL contextURL = ContextURL.with().type(type).asCollection().build(); + EntityCollectionSerializerOptions options = EntityCollectionSerializerOptions.with().contextURL(contextURL).build(); + + SerializerResult result = + odata.createSerializer(ODataFormat.fromContentType(responseFormat)) + .entityCollection(serviceMetadata, type, collection, options); + + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); } @Override @@ -230,7 +256,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor final UriResourceEntitySet resourceEntitySet = (UriResourceEntitySet) uriInfo.getUriResourceParts().get(0); final EdmEntitySet edmEntitySet = resourceEntitySet.getEntitySet(); final EdmEntityType edmEntityType = edmEntitySet.getEntityType(); - + final Entity entity; ExpandOption expand = null; if (edmEntityType.hasStream()) { // called from createMediaEntity(...), not directly @@ -238,14 +264,14 @@ public class TechnicalEntityProcessor extends TechnicalProcessor dataProvider.setMedia(entity, odata.createFixedFormatDeserializer().binary(request.getBody()), requestFormat.toContentTypeString()); } else { - final DeserializerResult deserializerResult = odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) - .entity(request.getBody(), edmEntityType); - new RequestValidator(dataProvider, - odata.createUriHelper(), - serviceMetadata.getEdm(), - request.getRawBaseUri() - ).validate(edmEntitySet, deserializerResult.getEntity()); - + final DeserializerResult deserializerResult = + odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) + .entity(request.getBody(), edmEntityType); + new RequestValidator(dataProvider, + odata.createUriHelper(), + serviceMetadata.getEdm(), + request.getRawBaseUri()).validate(edmEntitySet, deserializerResult.getEntity()); + entity = dataProvider.create(edmEntitySet); dataProvider.update(request.getRawBaseUri(), edmEntitySet, entity, deserializerResult.getEntity(), false, true); expand = deserializerResult.getExpandTree(); @@ -271,11 +297,11 @@ public class TechnicalEntityProcessor extends TechnicalProcessor throws ODataApplicationException, DeserializerException, SerializerException { final EdmEntitySet edmEntitySet = getEdmEntitySet(uriInfo); Entity entity; - + try { entity = readEntity(uriInfo); - } catch(ODataApplicationException e) { - if(e.getStatusCode() == HttpStatusCode.NOT_FOUND.getStatusCode()) { + } catch (ODataApplicationException e) { + if (e.getStatusCode() == HttpStatusCode.NOT_FOUND.getStatusCode()) { // Perform upsert createEntity(request, response, uriInfo, requestFormat, responseFormat); return; @@ -286,15 +312,14 @@ public class TechnicalEntityProcessor extends TechnicalProcessor checkRequestFormat(requestFormat); final ODataDeserializer deserializer = odata.createDeserializer(ODataFormat.fromContentType(requestFormat)); final Entity changedEntity = deserializer.entity(request.getBody(), edmEntitySet.getEntityType()).getEntity(); - - new RequestValidator(dataProvider, - true, // Update - request.getMethod() == HttpMethod.PATCH, - odata.createUriHelper(), - serviceMetadata.getEdm(), - request.getRawBaseUri() - ).validate(edmEntitySet, changedEntity); - + + new RequestValidator(dataProvider, + true, // Update + request.getMethod() == HttpMethod.PATCH, + odata.createUriHelper(), + serviceMetadata.getEdm(), + request.getRawBaseUri()).validate(edmEntitySet, changedEntity); + dataProvider.update(request.getRawBaseUri(), edmEntitySet, entity, changedEntity, request.getMethod() == HttpMethod.PATCH, false); response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); @@ -325,8 +350,36 @@ public class TechnicalEntityProcessor extends TechnicalProcessor public void processActionEntity(final ODataRequest request, ODataResponse response, final UriInfo uriInfo, final ContentType requestFormat, final ContentType responseFormat) throws ODataApplicationException, DeserializerException, SerializerException { - throw new ODataApplicationException("Any action returning an entity is not supported yet.", - HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + EdmAction action = checkBoundAndExtractAction(uriInfo); + DeserializerResult deserializerResult = + odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) + .actionParameters(request.getBody(), action); + + EntityActionResult entityResult = + dataProvider.processActionEntity(action.getName(), deserializerResult.getActionParameters()); + EdmEntityType type = (EdmEntityType) action.getReturnType().getType(); + if (entityResult == null || entityResult.getEntity() == null) { + if (action.getReturnType().isNullable()) { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } else { + // Not nullable return type so we have to give back a 500 + throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); + } + } else { + ContextURL contextURL = ContextURL.with().type(type).build(); + EntitySerializerOptions options = EntitySerializerOptions.with().contextURL(contextURL).build(); + + SerializerResult result = odata.createSerializer(ODataFormat.fromContentType(responseFormat)) + .entity(serviceMetadata, type, entityResult.getEntity(), options); + + if(entityResult.isCreated()){ + response.setStatusCode(HttpStatusCode.CREATED.getStatusCode()); + }else{ + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + } + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } } @Override 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 d8116705b..d847b320f 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 @@ -68,7 +68,6 @@ import org.apache.olingo.server.api.uri.UriHelper; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceAction; import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.UriResourceProperty; @@ -121,7 +120,7 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor Property property = dataProvider.processActionPrimitive(action.getName(), deserializerResult.getActionParameters()); EdmPrimitiveType type = (EdmPrimitiveType) action.getReturnType().getType(); - if (property.isNull()) { + if (property == null || property.isNull()) { if (action.getReturnType().isNullable()) { response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); } else { @@ -141,18 +140,6 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor } } - private EdmAction checkBoundAndExtractAction(final UriInfo uriInfo) throws ODataApplicationException { - final UriInfoResource resource = uriInfo.asUriInfoResource(); - List uriResourceParts = resource.getUriResourceParts(); - if (uriResourceParts.size() > 1) { - throw new ODataApplicationException("Bound acctions not supported yet.", - HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); - } - UriResourceAction uriResourceAction = (UriResourceAction) uriResourceParts.get(0); - EdmAction action = uriResourceAction.getAction(); - return action; - } - @Override public void readPrimitiveCollection(final ODataRequest request, ODataResponse response, final UriInfo uriInfo, final ContentType contentType) throws ODataApplicationException, SerializerException { @@ -177,8 +164,32 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor public void processActionPrimitiveCollection(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo, final ContentType requestFormat, final ContentType responseFormat) throws ODataApplicationException, DeserializerException, SerializerException { - throw new ODataApplicationException("Not supported yet.", - HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + EdmAction action = checkBoundAndExtractAction(uriInfo); + DeserializerResult deserializerResult = + odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) + .actionParameters(request.getBody(), action); + + Property property = + dataProvider.processActionPrimitiveCollection(action.getName(), deserializerResult.getActionParameters()); + + if (property == null || property.isNull()) { + // Collection Propertys must never be null + throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); + } else if (property.asCollection().contains(null) && !action.getReturnType().isNullable()) { + // Not nullable return type but array contains a null value + throw new ODataApplicationException("The action could no be executed", 500, 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(type, property, options); + + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); } @Override @@ -199,8 +210,32 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor public void processActionComplex(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, DeserializerException, SerializerException { - throw new ODataApplicationException("Not supported yet.", - HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + EdmAction action = checkBoundAndExtractAction(uriInfo); + DeserializerResult deserializerResult = + odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) + .actionParameters(request.getBody(), action); + + Property property = dataProvider.processActionComplex(action.getName(), deserializerResult.getActionParameters()); + EdmComplexType type = (EdmComplexType) action.getReturnType().getType(); + if (property == null || property.isNull()) { + if (action.getReturnType().isNullable()) { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } else { + // Not nullable return type so we have to give back a 500 + throw new ODataApplicationException("The action could no be executed", 500, 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()); + } } @Override @@ -232,9 +267,10 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) .actionParameters(request.getBody(), action); - Property property = dataProvider.processActionPrimitive(action.getName(), deserializerResult.getActionParameters()); + Property property = + dataProvider.processActionComplexCollection(action.getName(), deserializerResult.getActionParameters()); - if (property.isNull()) { + if (property == null || property.isNull()) { // Collection Propertys must never be null throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); } else if (property.asCollection().contains(null) && !action.getReturnType().isNullable()) { diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java index d826b19ff..f2a1f236f 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java @@ -24,6 +24,7 @@ import java.util.Locale; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Link; +import org.apache.olingo.commons.api.edm.EdmAction; import org.apache.olingo.commons.api.edm.EdmBindingTarget; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmEntityType; @@ -34,9 +35,11 @@ import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.processor.Processor; +import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceAction; import org.apache.olingo.server.api.uri.UriResourceEntitySet; import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.UriResourceNavigation; @@ -212,4 +215,16 @@ public abstract class TechnicalProcessor implements Processor { HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); } } + + protected EdmAction checkBoundAndExtractAction(final UriInfo uriInfo) throws ODataApplicationException { + final UriInfoResource resource = uriInfo.asUriInfoResource(); + List uriResourceParts = resource.getUriResourceParts(); + if (uriResourceParts.size() > 1) { + throw new ODataApplicationException("Bound acctions not supported yet.", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + UriResourceAction uriResourceAction = (UriResourceAction) uriResourceParts.get(0); + EdmAction action = uriResourceAction.getAction(); + return action; + } } From 87fa79ad933dd85ae875a19e5b3171101e0585fe Mon Sep 17 00:00:00 2001 From: Christian Amend Date: Mon, 27 Apr 2015 14:48:56 +0200 Subject: [PATCH 3/4] [OLINGO-604] Ignore odata annotations for action requests --- .../deserializer/json/ODataJsonDeserializer.java | 16 ++++++++++++++++ ...DataJsonDeserializerActionParametersTest.java | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java index 66da3fcfa..a6c3e8d15 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java @@ -195,6 +195,22 @@ public class ODataJsonDeserializer implements ODataDeserializer { Map parameters = new LinkedHashMap(); if (tree != null) { consumeParameters(edmAction, tree, parameters); + + final List toRemove = new ArrayList(); + Iterator> fieldsIterator = tree.fields(); + while (fieldsIterator.hasNext()) { + Map.Entry field = fieldsIterator.next(); + + if (field.getKey().contains(ODATA_CONTROL_INFORMATION_PREFIX)) { + // Control Information is ignored for requests as per specification chapter "4.5 Control Information" + toRemove.add(field.getKey()); + } else if (field.getKey().contains(ODATA_ANNOTATION_MARKER)) { + throw new DeserializerException("Custom annotation with field name: " + field.getKey() + " not supported", + DeserializerException.MessageKeys.NOT_IMPLEMENTED); + } + } + // remove here to avoid iterator issues. + tree.remove(toRemove); assertJsonNodeIsEmpty(tree); } return DeserializerResultImpl.with().actionParameters(parameters).build(); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java index 01166a220..9df2b3405 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java @@ -65,6 +65,22 @@ public class ODataJsonDeserializerActionParametersTest extends AbstractODataDese assertTrue(parameters.isEmpty()); } + @Test + public void ignoreODataAnnotations() throws Exception { + final String input = + "{\"ParameterDuration@odata.type\":\"Edm.Duration\"," + + "\"ParameterDuration\":\"P42DT11H22M33S\",\"ParameterInt16\":42}"; + final Map parameters = deserialize(input, "UARTTwoParam"); + assertNotNull(parameters); + assertEquals(2, parameters.size()); + Parameter parameter = parameters.get("ParameterInt16"); + assertNotNull(parameter); + assertEquals((short) 42, parameter.getValue()); + parameter = parameters.get("ParameterDuration"); + assertNotNull(parameter); + assertEquals(BigDecimal.valueOf(3669753), parameter.getValue()); + } + @Test(expected = DeserializerException.class) public void bindingParameter() throws Exception { deserialize("{\"ParameterETAllPrim\":{\"PropertyInt16\":42}}", "BAETAllPrimRT", "ETAllPrim"); From b78343bd09e6b73bd6777c4ae90971dd109a6fcb Mon Sep 17 00:00:00 2001 From: Ramesh Reddy Date: Mon, 27 Apr 2015 11:16:34 -0500 Subject: [PATCH 4/4] OLINGO-573: validating to make sure key predicates are supplied for update, delete entity requests --- .../server/core/requests/DataRequest.java | 5 +++ .../server/example/TripPinDataModel.java | 42 +++++++++++++++++++ .../olingo/server/example/TripPinHandler.java | 20 ++++++++- .../server/example/TripPinServiceTest.java | 27 ++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java index b3be91c15..98daf7a00 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java @@ -273,6 +273,11 @@ public class DataRequest extends ServiceRequest { return false; } + // in update, delete entity cases, predicate must be there + if ((isPATCH() || isPUT() || isDELETE()) + && (getKeyPredicates() == null || getKeyPredicates().isEmpty())) { + return false; + } return true; } diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinDataModel.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinDataModel.java index 78bd012a5..290f38d5c 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinDataModel.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinDataModel.java @@ -630,6 +630,48 @@ public class TripPinDataModel { return copy; } + public boolean updateEntity(EdmEntitySet edmEntitySet, String eTag, String key, Object keyValue, + boolean merge, Entity changes, String baseURL) throws ODataApplicationException { + boolean updated = false; + + if (merge) { + EntityCollection set = getEntitySet(edmEntitySet.getName()); + Iterator it = set.getEntities().iterator(); + while (it.hasNext()) { + Entity entity = it.next(); + if (entity.getProperty(key).getValue().equals(keyValue) && eTag.equals("*") + || eTag.equals(entity.getETag())) { + + for (Property p :changes.getProperties()) { + for (Property p1: entity.getProperties()) { + if (p.getName().equals(p1.getName())) { + p1.setValue(p1.getValueType(), p.getValue()); + updated = true; + break; + } + } + } + break; + } + } + } else { + // this is delete, then insert + EntityCollection set = getEntitySet(edmEntitySet.getName()); + Iterator it = set.getEntities().iterator(); + while (it.hasNext()) { + Entity entity = it.next(); + if (entity.getProperty(key).getValue().equals(keyValue) && eTag.equals("*") + || eTag.equals(entity.getETag())) { + Property p = entity.getProperty(key); + changes.addProperty(p); + createEntity(edmEntitySet, changes, baseURL); + updated = true; + } + } + } + return updated; + } + public boolean deleteEntity(String entitySetName, String eTag, String key, Object keyValue) { EntityCollection set = getEntitySet(entitySetName); Iterator it = set.getEntities().iterator(); diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinHandler.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinHandler.java index c180a0692..df2b645b5 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinHandler.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinHandler.java @@ -273,9 +273,25 @@ public class TripPinHandler implements ServiceHandler { } @Override - public void updateEntity(DataRequest request, Entity entity, boolean merge, String entityETag, + public void updateEntity(DataRequest request, Entity entity, boolean merge, String eTag, EntityResponse response) throws ODataTranslatedException, ODataApplicationException { - response.writeServerError(true); + EdmEntitySet edmEntitySet = request.getEntitySet(); + + Entity currentEntity = this.dataModel.getEntity(edmEntitySet.getName(), request.getKeyPredicates()); + if (currentEntity == null) { + response.writeNotFound(true); + return; + } + String key = edmEntitySet.getEntityType().getKeyPredicateNames().get(0); + String baseURL = request.getODataRequest().getRawBaseUri(); + boolean updated = this.dataModel.updateEntity(edmEntitySet, eTag, key, currentEntity + .getProperty(key).getValue(), merge, entity, baseURL); + + if (updated) { + response.writeUpdatedEntity(); + } else { + response.writeNotModified(); + } } @Override diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java index c64bfb575..9476f0987 100644 --- a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java @@ -34,6 +34,7 @@ import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ByteArrayEntity; @@ -418,6 +419,32 @@ public class TripPinServiceTest { EntityUtils.consumeQuietly(response.getEntity()); } + @Test + public void testUpdateEntity() throws Exception { + String payload = "{" + + " \"Emails\":[" + + " \"Krista@example.com\"," + + " \"Krista@gmail.com\"" + + " ]" + + "}"; + HttpPatch updateRequest = new HttpPatch(baseURL+"/People('kristakemp')"); + updateRequest.setEntity(new StringEntity(payload, ContentType.APPLICATION_JSON)); + httpSend(updateRequest, 204); + + HttpResponse response = httpGET(baseURL + "/People('kristakemp')", 200); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People/$entity", node.get("@odata.context").asText()); + assertEquals("Krista@example.com", node.get("Emails").get(0).asText()); + assertEquals("Krista@gmail.com", node.get("Emails").get(1).asText()); + } + + @Test + public void testDeleteEntity() throws Exception{ + // fail because no key predicates supplied + HttpDelete deleteRequest = new HttpDelete(baseURL+"/People"); + HttpResponse response = httpSend(deleteRequest, 405); + EntityUtils.consumeQuietly(response.getEntity()); + } @Test public void testCreateEntityWithLinkToRelatedEntities() throws Exception {