From 72dc69dc8e2b5270ab9dbbe40e6bd3b7e5e248ba Mon Sep 17 00:00:00 2001 From: fmartelli Date: Mon, 28 Jul 2014 17:48:57 +0200 Subject: [PATCH] [OLINGO-365] provided more tests --- .../ext/proxy/api/EntityCollection.java | 15 ++ .../olingo/ext/proxy/api/EntityType.java | 14 ++ .../ext/proxy/api/PrimitiveCollection.java | 2 + .../AbstractCollectionInvocationHandler.java | 32 ++++- .../commons/AbstractPersistenceManager.java | 130 ++++++++++++------ .../AbstractStructuredInvocationHandler.java | 18 ++- .../EntityCollectionInvocationHandler.java | 2 +- .../commons/EntityInvocationHandler.java | 9 ++ .../PrimitiveCollectionInvocationHandler.java | 7 + .../ext/proxy/context/EntityLinkDesc.java | 19 +++ .../org/apache/olingo/fit/V4Services.java | 11 ++ .../Q3VzdG9tZXJzKDEpL09yZGVycw==.full.json | 8 ++ .../proxy/v4/APIBasicDesignTestITCase.java | 103 +++++++++++++- .../fit/proxy/v4/EntityUpdateTestITCase.java | 2 +- .../request/cud/v4/CUDRequestFactory.java | 12 ++ .../cud/v4/ODataReferenceAddingRequest.java | 30 ++++ .../v4/ODataReferenceAddingResponse.java | 29 ++++ .../request/cud/v4/CUDRequestFactoryImpl.java | 6 + .../v4/ODataReferenceAddingRequestImpl.java | 92 +++++++++++++ 19 files changed, 492 insertions(+), 49 deletions(-) create mode 100644 fit/src/main/resources/V40/references/Q3VzdG9tZXJzKDEpL09yZGVycw==.full.json create mode 100644 lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/ODataReferenceAddingRequest.java create mode 100644 lib/client-api/src/main/java/org/apache/olingo/client/api/communication/response/v4/ODataReferenceAddingResponse.java create mode 100644 lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/ODataReferenceAddingRequestImpl.java diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityCollection.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityCollection.java index 587b9f131..9ff7fe560 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityCollection.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityCollection.java @@ -23,4 +23,19 @@ import java.util.Collection; public interface EntityCollection< T extends StructuredType, EC extends Collection, CT extends StructuredCollection> extends StructuredCollection { + + /** + * Appends ref segment to the URI. + * + * @return the same query instance. + */ + CT refs(); + + /** + * Add entity by its reference ID. + * + * @param element entity to be linked. + * @return TRUE if correctly added; FALSE otherwise. + */ + > boolean addRef(ET element); } diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityType.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityType.java index 45f375d67..891b2db86 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityType.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/EntityType.java @@ -19,4 +19,18 @@ package org.apache.olingo.ext.proxy.api; public interface EntityType> extends StructuredType { + + /** + * Appends ref segment to the URI. + * + * @return the same query instance. + */ + T refs(); + + /** + * Gets entity reference ID. + * + * @return entity reference ID. + */ + String getEntityReferenceID(); } diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/PrimitiveCollection.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/PrimitiveCollection.java index 4ec4a5421..329dce831 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/PrimitiveCollection.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/PrimitiveCollection.java @@ -26,4 +26,6 @@ public interface PrimitiveCollection CollectionQuery>, Collection, Serializable { + + void delete(); } diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractCollectionInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractCollectionInvocationHandler.java index 73369f8bb..f1b9eb5ab 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractCollectionInvocationHandler.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractCollectionInvocationHandler.java @@ -33,9 +33,13 @@ import java.util.concurrent.Callable; import java.util.concurrent.Future; import org.apache.commons.lang3.tuple.Triple; import org.apache.olingo.client.api.uri.URIFilter; +import org.apache.olingo.client.api.uri.v4.URIBuilder; import org.apache.olingo.commons.api.domain.v4.ODataAnnotation; +import org.apache.olingo.commons.api.domain.v4.ODataEntity; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; import org.apache.olingo.ext.proxy.AbstractService; import org.apache.olingo.ext.proxy.api.AbstractTerm; +import org.apache.olingo.ext.proxy.api.EntityType; import org.apache.olingo.ext.proxy.api.Sort; import org.apache.olingo.ext.proxy.api.annotations.Namespace; import org.apache.olingo.ext.proxy.api.annotations.Term; @@ -50,6 +54,8 @@ public abstract class AbstractCollectionInvocationHandler items; + protected Collection referenceItems; + protected final URI baseURI; protected CommonURIBuilder uri; @@ -71,13 +77,13 @@ public abstract class AbstractCollectionInvocationHandler(); this.uri = uri; this.baseURI = this.uri == null ? null : this.uri.build(); } public Future> executeAsync() { return service.getClient().getConfiguration().getExecutor().submit(new Callable>() { - @Override public Collection call() throws Exception { return execute(); @@ -173,6 +179,30 @@ public abstract class AbstractCollectionInvocationHandler> boolean addRef(final ET element) { + if (getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0) { + return false; + } + + if (element instanceof Proxy && Proxy.getInvocationHandler(element) instanceof EntityInvocationHandler) { + final EntityInvocationHandler handler = EntityInvocationHandler.class.cast(Proxy.getInvocationHandler(element)); + final URI id = ((ODataEntity) handler.getEntity()).getId(); + if (id == null) { + return false; + } + + return referenceItems.add(id.toASCIIString()); + } + + return false; + } + + public void refs() { + if (getClient().getServiceVersion().compareTo(ODataServiceVersion.V40) >= 0) { + ((URIBuilder) this.uri).appendRefSegment(); + } + } + @Override public int size() { return items.size(); diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java index c01af7642..62b701a78 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java @@ -18,6 +18,7 @@ */ package org.apache.olingo.ext.proxy.commons; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.net.URI; import java.util.ArrayList; @@ -33,6 +34,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.olingo.client.api.communication.header.ODataPreferences; import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest; import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest; +import org.apache.olingo.client.api.communication.request.cud.v4.ODataReferenceAddingRequest; import org.apache.olingo.client.api.communication.request.streamed.ODataMediaEntityUpdateRequest; import org.apache.olingo.client.api.communication.request.streamed.ODataStreamUpdateRequest; import org.apache.olingo.client.core.uri.URIUtils; @@ -71,7 +73,6 @@ abstract class AbstractPersistenceManager implements PersistenceManager { @Override public Future> flushAsync() { return service.getClient().getConfiguration().getExecutor().submit(new Callable>() { - @Override public List call() throws Exception { return flush(); @@ -93,7 +94,6 @@ abstract class AbstractPersistenceManager implements PersistenceManager { if (((status != AttachedEntityStatus.ATTACHED && status != AttachedEntityStatus.LINKED) || attachedEntity.getEntity().isChanged()) && !items.contains(attachedEntity.getEntity())) { - pos++; pos = processEntityContext(attachedEntity.getEntity(), pos, items, delayedUpdates, changes); } @@ -224,6 +224,16 @@ abstract class AbstractPersistenceManager implements PersistenceManager { if (!toBeLinked.isEmpty()) { delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, toBeLinked, type)); } + + if (property.getValue() instanceof Proxy) { + final InvocationHandler target = Proxy.getInvocationHandler(property.getValue()); + + if (target instanceof EntityCollectionInvocationHandler) { + for (String ref : ((EntityCollectionInvocationHandler) target).referenceItems) { + delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, ref)); + } + } + } } if (entity instanceof ODataEntity) { @@ -253,7 +263,7 @@ abstract class AbstractPersistenceManager implements PersistenceManager { final URI targetURI = currentStatus == AttachedEntityStatus.NEW ? URI.create("$" + startingPos) : URIUtils.getURI( - service.getClient().getServiceRoot(), handler.getEntity().getEditLink().toASCIIString()); + service.getClient().getServiceRoot(), handler.getEntity().getEditLink().toASCIIString()); queueUpdate(handler, targetURI, entity, changeset); pos++; items.put(handler, pos); @@ -265,8 +275,8 @@ abstract class AbstractPersistenceManager implements PersistenceManager { final URI targetURI = currentStatus == AttachedEntityStatus.NEW ? URI.create("$" + startingPos + "/$value") : URIUtils.getURI( - service.getClient().getServiceRoot(), - handler.getEntity().getEditLink().toASCIIString() + "/$value"); + service.getClient().getServiceRoot(), + handler.getEntity().getEditLink().toASCIIString() + "/$value"); queueUpdateMediaEntity(handler, targetURI, handler.getStreamChanges(), changeset); @@ -280,8 +290,8 @@ abstract class AbstractPersistenceManager implements PersistenceManager { for (Map.Entry streamedChanges : handler.getStreamedPropertyChanges().entrySet()) { final URI targetURI = currentStatus == AttachedEntityStatus.NEW ? URI.create("$" + startingPos) : URIUtils.getURI( - service.getClient().getServiceRoot(), - CoreUtils.getMediaEditLink(streamedChanges.getKey(), entity).toASCIIString()); + service.getClient().getServiceRoot(), + CoreUtils.getMediaEditLink(streamedChanges.getKey(), entity).toASCIIString()); queueUpdateMediaResource(handler, targetURI, streamedChanges.getValue(), changeset); @@ -302,46 +312,60 @@ abstract class AbstractPersistenceManager implements PersistenceManager { final PersistenceChanges changeset) { for (EntityLinkDesc delayedUpdate : delayedUpdates) { - pos++; - items.put(delayedUpdate.getSource(), pos); + if (StringUtils.isBlank(delayedUpdate.getReference())) { - final CommonODataEntity changes = - service.getClient().getObjectFactory().newEntity(delayedUpdate.getSource().getEntity().getTypeName()); + pos++; + items.put(delayedUpdate.getSource(), pos); - AttachedEntityStatus status = service.getContext().entityContext().getStatus(delayedUpdate.getSource()); + final CommonODataEntity changes = + service.getClient().getObjectFactory().newEntity(delayedUpdate.getSource().getEntity().getTypeName()); - final URI sourceURI; - if (status == AttachedEntityStatus.CHANGED) { - sourceURI = URIUtils.getURI( - service.getClient().getServiceRoot(), - delayedUpdate.getSource().getEntity().getEditLink().toASCIIString()); - } else { - int sourcePos = items.get(delayedUpdate.getSource()); - sourceURI = URI.create("$" + sourcePos); - } + AttachedEntityStatus status = service.getContext().entityContext().getStatus(delayedUpdate.getSource()); - for (EntityInvocationHandler target : delayedUpdate.getTargets()) { - status = service.getContext().entityContext().getStatus(target); - - final URI targetURI; + final URI sourceURI; if (status == AttachedEntityStatus.CHANGED) { - targetURI = URIUtils.getURI( - service.getClient().getServiceRoot(), target.getEntity().getEditLink().toASCIIString()); + sourceURI = URIUtils.getURI( + service.getClient().getServiceRoot(), + delayedUpdate.getSource().getEntity().getEditLink().toASCIIString()); } else { - int targetPos = items.get(target); - targetURI = URI.create("$" + targetPos); + int sourcePos = items.get(delayedUpdate.getSource()); + sourceURI = URI.create("$" + sourcePos); } - changes.addLink(delayedUpdate.getType() == ODataLinkType.ENTITY_NAVIGATION - ? service.getClient().getObjectFactory(). - newEntityNavigationLink(delayedUpdate.getSourceName(), targetURI) - : service.getClient().getObjectFactory(). - newEntitySetNavigationLink(delayedUpdate.getSourceName(), targetURI)); + for (EntityInvocationHandler target : delayedUpdate.getTargets()) { + status = service.getContext().entityContext().getStatus(target); - LOG.debug("'{}' from {} to {}", delayedUpdate.getType().name(), sourceURI, targetURI); + final URI targetURI; + if (status == AttachedEntityStatus.CHANGED) { + targetURI = URIUtils.getURI( + service.getClient().getServiceRoot(), target.getEntity().getEditLink().toASCIIString()); + } else { + int targetPos = items.get(target); + targetURI = URI.create("$" + targetPos); + } + + changes.addLink(delayedUpdate.getType() == ODataLinkType.ENTITY_NAVIGATION + ? service.getClient().getObjectFactory(). + newEntityNavigationLink(delayedUpdate.getSourceName(), targetURI) + : service.getClient().getObjectFactory(). + newEntitySetNavigationLink(delayedUpdate.getSourceName(), targetURI)); + + LOG.debug("'{}' from {} to {}", delayedUpdate.getType().name(), sourceURI, targetURI); + } + + queueUpdate(delayedUpdate.getSource(), sourceURI, changes, changeset); + } else { + URI sourceURI = URIUtils.getURI( + service.getClient().getServiceRoot(), + delayedUpdate.getSource().getEntity().getEditLink().toASCIIString() + + "/" + delayedUpdate.getSourceName() + "/$ref"); + + if (queueUpdateLinkViaRef( + delayedUpdate.getSource(), sourceURI, URI.create(delayedUpdate.getReference()), changeset)) { + pos++; + items.put(delayedUpdate.getSource(), pos); + } } - - queueUpdate(delayedUpdate.getSource(), sourceURI, changes, changeset); } } @@ -435,10 +459,10 @@ abstract class AbstractPersistenceManager implements PersistenceManager { service.getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0 ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) service.getClient()).getCUDRequestFactory(). getEntityUpdateRequest(handler.getEntityURI(), - org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes) + org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes) : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) service.getClient()).getCUDRequestFactory(). getEntityUpdateRequest(handler.getEntityURI(), - org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes); + org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes); req.setPrefer(new ODataPreferences(service.getClient().getServiceVersion()).returnContent()); @@ -449,6 +473,30 @@ abstract class AbstractPersistenceManager implements PersistenceManager { changeset.addChange(req, handler); } + private boolean queueUpdateLinkViaRef( + final EntityInvocationHandler handler, + final URI source, + final URI targetRef, + final PersistenceChanges changeset) { + + LOG.debug("Update '{}'", targetRef); + if (service.getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) >= 1) { + final ODataReferenceAddingRequest req = + ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) service.getClient()).getCUDRequestFactory(). + getReferenceAddingRequest(source, targetRef); + + req.setPrefer(new ODataPreferences(service.getClient().getServiceVersion()).returnContent()); + + if (StringUtils.isNotBlank(handler.getETag())) { + req.setIfMatch(handler.getETag()); + } + + changeset.addChange(req, handler); + return true; + } + return false; + } + private void queueUpdate( final EntityInvocationHandler handler, final URI uri, @@ -461,10 +509,10 @@ abstract class AbstractPersistenceManager implements PersistenceManager { service.getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0 ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) service.getClient()).getCUDRequestFactory(). getEntityUpdateRequest(uri, - org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes) + org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes) : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) service.getClient()).getCUDRequestFactory(). getEntityUpdateRequest(uri, - org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes); + org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes); req.setPrefer(new ODataPreferences(service.getClient().getServiceVersion()).returnContent()); diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java index 72cf7ecc4..b1835a395 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java @@ -37,6 +37,7 @@ import java.util.concurrent.Callable; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.olingo.client.api.uri.CommonURIBuilder; +import org.apache.olingo.client.api.uri.v4.URIBuilder; import org.apache.olingo.client.core.uri.URIUtils; import org.apache.olingo.commons.api.domain.CommonODataEntity; import org.apache.olingo.commons.api.domain.CommonODataProperty; @@ -47,6 +48,7 @@ import org.apache.olingo.commons.api.domain.ODataLinked; import org.apache.olingo.commons.api.domain.ODataValue; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; import org.apache.olingo.ext.proxy.AbstractService; import org.apache.olingo.ext.proxy.api.AbstractEntitySet; import org.apache.olingo.ext.proxy.api.ComplexCollection; @@ -156,7 +158,9 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { - if ("expand".equals(method.getName()) || "select".equals(method.getName())) { + if ("expand".equals(method.getName()) + || "select".equals(method.getName()) + || "refs".equals(method.getName())) { invokeSelfMethod(method, args); return proxy; } else if (isSelfMethod(method, args)) { @@ -238,7 +242,8 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca public void delete(final String name) { if (baseURI != null) { getContext().entityContext().addFurtherDeletes( - getClient().newURIBuilder(baseURI.toASCIIString()).appendPropertySegment(name).build()); + getClient().newURIBuilder(baseURI.toASCIIString()).appendPropertySegment(name).appendValueSegment(). + build()); } } @@ -254,7 +259,8 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca entityContext.attach(handler, AttachedEntityStatus.DELETED); } } else if (baseURI != null) { - entityContext.addFurtherDeletes(baseURI); + entityContext.addFurtherDeletes( + getClient().newURIBuilder(baseURI.toASCIIString()).appendValueSegment().build()); } } @@ -632,6 +638,12 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca this.uri.select(select); } + public void refs() { + if (getClient().getServiceVersion().compareTo(ODataServiceVersion.V40) >= 0) { + ((URIBuilder) this.uri).appendRefSegment(); + } + } + public void clearQueryOptions() { this.uri = baseURI == null ? null : getClient().newURIBuilder(baseURI.toASCIIString()); } diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java index 6f5478153..1a16888a9 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java @@ -59,8 +59,8 @@ public class EntityCollectionInvocationHandler> || "expand".equals(method.getName()) || "select".equals(method.getName()) || "nextPage".equals(method.getName()) + || "refs".equals(method.getName()) || "execute".equals(method.getName())) { - invokeSelfMethod(method, args); return proxy; } else if (isSelfMethod(method, args)) { diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java index 7cd8d3c7e..054604bb9 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java @@ -508,6 +508,15 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler return getEntity() == null ? null : getEntity().getProperty(name); } + public String getEntityReferenceID() { + URI id = getEntity() == null ? null + : getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0 + ? ((org.apache.olingo.commons.api.domain.v3.ODataEntity) getEntity()).getLink() + : ((org.apache.olingo.commons.api.domain.v4.ODataEntity) getEntity()).getId(); + + return id == null ? null : id.toASCIIString(); + } + @Override public String toString() { return uuid.toString(); diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PrimitiveCollectionInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PrimitiveCollectionInvocationHandler.java index 4da4b1a28..48908c9b1 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PrimitiveCollectionInvocationHandler.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PrimitiveCollectionInvocationHandler.java @@ -114,6 +114,13 @@ public class PrimitiveCollectionInvocationHandler resItems, null, Collections.emptyList()); } + public void delete() { + if (baseURI != null) { + getContext().entityContext().addFurtherDeletes( + getClient().newURIBuilder(baseURI.toASCIIString()).appendValueSegment().build()); + } + } + @Override public boolean equals(final Object obj) { if (obj instanceof Proxy) { diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java index b96dc2df0..5a5318138 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java @@ -41,6 +41,8 @@ public class EntityLinkDesc implements Serializable { private final ODataLinkType type; + private final String reference; + public EntityLinkDesc( final String sourceName, final EntityInvocationHandler source, @@ -50,6 +52,7 @@ public class EntityLinkDesc implements Serializable { this.source = source; this.targets = target; this.type = type; + this.reference = null; } public EntityLinkDesc( @@ -61,6 +64,18 @@ public class EntityLinkDesc implements Serializable { this.source = source; this.targets = Collections.singleton(target); this.type = type; + this.reference = null; + } + + public EntityLinkDesc( + final String sourceName, + final EntityInvocationHandler source, + final String targetRef) { + this.sourceName = sourceName; + this.source = source; + this.targets = null; + this.type = null; + this.reference = targetRef; } public String getSourceName() { @@ -79,6 +94,10 @@ public class EntityLinkDesc implements Serializable { return type; } + public String getReference() { + return reference; + } + /** * {@inheritDoc } */ diff --git a/fit/src/main/java/org/apache/olingo/fit/V4Services.java b/fit/src/main/java/org/apache/olingo/fit/V4Services.java index 347705d6e..5b59ddc6f 100644 --- a/fit/src/main/java/org/apache/olingo/fit/V4Services.java +++ b/fit/src/main/java/org/apache/olingo/fit/V4Services.java @@ -1365,6 +1365,17 @@ public class V4Services extends AbstractServices { return xml.createResponse(null, null, null, Status.NO_CONTENT); } + @POST + @Path("/Customers(1)/Orders/$ref") + public Response linkOrderViaRef( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + final String entity) { + + return xml.createResponse(null, null, null, Status.NO_CONTENT); + } + @DELETE @Path("/Products({productId})/Categories({categoryId})/$ref") public Response deleteLinked( diff --git a/fit/src/main/resources/V40/references/Q3VzdG9tZXJzKDEpL09yZGVycw==.full.json b/fit/src/main/resources/V40/references/Q3VzdG9tZXJzKDEpL09yZGVycw==.full.json new file mode 100644 index 000000000..0ff0ae370 --- /dev/null +++ b/fit/src/main/resources/V40/references/Q3VzdG9tZXJzKDEpL09yZGVycw==.full.json @@ -0,0 +1,8 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/stub/StaticService/V40/Static.svc/$metadata#$ref", + "value": + [ + {"@odata.id": "http://localhost:${cargo.servlet.port}/stub/StaticService/V40/Static.svc/Orders(7)"}, + {"@odata.id": "http://localhost:${cargo.servlet.port}/stub/StaticService/V40/Static.svc/Orders(8)"} + ] +} \ No newline at end of file diff --git a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java index 5150268cf..15076d035 100644 --- a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java @@ -54,8 +54,10 @@ import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.service import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.AddressCollection; import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.Color; import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.Customer; +import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.CustomerCollection; import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.HomeAddress; import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.Order; +import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.OrderCollection; import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.Person; import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.PersonCollection; import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.Product; @@ -74,6 +76,46 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase { return container; } + @Test + public void readEntitySet() { + final OrderCollection orders = container.getOrders().execute(); + assertFalse(orders.isEmpty()); + + final CustomerCollection customers = container.getCustomers(). + orderBy("PersonID"). + select("FirstName", "LastName", "Orders"). + expand("Orders"). + execute(); + + assertEquals(2, customers.size()); + for (Customer customer : customers) { + assertNotNull(customer.getFirstName()); + assertNotNull(customer.getLastName()); + } + } + + @Test + public void readWithReferences() { + final Person person = container.getOrders().getByKey(8).getCustomerForOrder().refs().load(); + assertEquals("http://localhost:9080/stub/StaticService/V40/Static.svc/Customers(PersonID=1)", + person.getEntityReferenceID()); + + final OrderCollection orders = container.getCustomers().getByKey(1).getOrders().refs().execute(); + assertEquals("http://localhost:9080/stub/StaticService/V40/Static.svc/Orders(7)", + orders.iterator().next().getEntityReferenceID()); + } + + @Test + public void addViaReference() { + final Order order = container.getOrders().getByKey(8).load(); + + final OrderCollection orders = container.newEntityCollection(OrderCollection.class); + orders.addRef(order); + + container.getCustomers().getByKey(1).setOrders(orders); + container.flush(); + } + @Test public void readAndCheckForPrimitive() { final Customer customer = getContainer().getCustomers().getByKey(1); @@ -243,6 +285,42 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase { service.getContext().detachAll(); // avoid influences } + @Test + public void deleteSingleProperty() { + container.getCustomers().getByKey(1).delete("City"); + container.flush(); + } + + @Test + public void deleteComplex() { + container.getCustomers().getByKey(1).getHomeAddress().delete(); + container.flush(); + } + + @Test + public void deleteCollection() { + container.getCustomers().getByKey(1).getEmails().delete(); + container.flush(); + } + + @Test + public void deleteEdmStreamProperty() throws IOException { + // --------------------------------------- + // Instantiate Demo Service + // --------------------------------------- + final org.apache.olingo.fit.proxy.v4.demo.Service dservice = + org.apache.olingo.fit.proxy.v4.demo.Service.getV4(testDemoServiceRootURL); + dservice.getClient().getConfiguration().setDefaultBatchAcceptFormat(ContentType.APPLICATION_OCTET_STREAM); + final DemoService dcontainer = dservice.getEntityContainer(DemoService.class); + assertNotNull(dcontainer); + dservice.getContext().detachAll(); + // --------------------------------------- + dcontainer.getPersonDetails().getByKey(1).delete("Photo"); + dcontainer.flush(); + + dservice.getContext().detachAll(); // avoid influences + } + @Test public void updateComplexProperty() { Address homeAddress = container.getCustomers().getByKey(1).getHomeAddress(); @@ -327,7 +405,7 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase { // --------------------------------------- org.apache.olingo.fit.proxy.v3.staticservice.Service v3serv = org.apache.olingo.fit.proxy.v3.staticservice.Service.getV3( - "http://localhost:9080/stub/StaticService/V30/Static.svc"); + "http://localhost:9080/stub/StaticService/V30/Static.svc"); v3serv.getClient().getConfiguration().setDefaultBatchAcceptFormat(ContentType.APPLICATION_OCTET_STREAM); final DefaultContainer v3cont = v3serv.getEntityContainer(DefaultContainer.class); assertNotNull(v3cont); @@ -378,11 +456,32 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase { // container.getOrders().getByKey(1).getCustomerForOrder().getEmails().execute().isEmpty()); // Not supported by the test service BTW generates a single request as expected: // /Orders(1)/CustomerForOrder/Emails + + emails.add("fabio.martelli@tirasa.net"); + container.getPeople().getByKey(1).setEmails(emails); + + container.flush(); + + boolean found = false; + for (String email : container.getPeople().getByKey(1).getEmails().execute()) { + if (email.equals("fabio.martelli@tirasa.net")) { + found = true; + } + } + + assertTrue(found); + + getService().getContext().detachAll(); } @Test public void workingWithSingletons() { assertNotNull(container.getCompany().getVipCustomer().load().getPersonID()); + + container.getCompany().setName("new name"); + container.flush(); + + assertEquals("new name", container.getCompany().load().getName()); } @Test @@ -424,7 +523,7 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase { final AddressCollection ac = container.newComplexCollection(AddressCollection.class); final Person updated = container.getCustomers().getByKey(2).operations(). resetAddress(ac, 0).select("Name").expand("Orders").execute(); - assertNotNull(person); + assertNotNull(updated); } @Test diff --git a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java index d2fd6b476..863d0417d 100644 --- a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java @@ -62,7 +62,7 @@ public class EntityUpdateTestITCase extends AbstractTestITCase { @Test public void update() { - Person person = getContainer().getPeople().getByKey(1).load(); + Person person = getContainer().getPeople().getByKey(1); final Address address = person.getHomeAddress(); address.setCity("XXX"); diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/CUDRequestFactory.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/CUDRequestFactory.java index 14bf85a3f..665280982 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/CUDRequestFactory.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/CUDRequestFactory.java @@ -32,4 +32,16 @@ public interface CUDRequestFactory extends CommonCUDRequestFactory { ODataEntityUpdateRequest getSingletonUpdateRequest( UpdateType type, ODataSingleton entity); + /** + * A successful POST request to a navigation property's references collection adds a relationship to an existing + * entity. The request body MUST contain a single entity reference that identifies the entity to be added. See the + * appropriate format document for details. On successful completion, the response MUST be 204 No Content and contain + * an empty body. + * + * @param concrete ODataEntity implementation + * @param targetURI entity set URI. + * @param entity entity to be created. + * @return new ODataEntityCreateRequest instance. + */ + ODataReferenceAddingRequest getReferenceAddingRequest(URI targetURI, URI reference); } diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/ODataReferenceAddingRequest.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/ODataReferenceAddingRequest.java new file mode 100644 index 000000000..d107eb32e --- /dev/null +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/v4/ODataReferenceAddingRequest.java @@ -0,0 +1,30 @@ +/* + * 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.client.api.communication.request.cud.v4; + +import org.apache.olingo.client.api.communication.request.ODataBasicRequest; +import org.apache.olingo.client.api.communication.request.ODataBatchableRequest; +import org.apache.olingo.client.api.communication.response.v4.ODataReferenceAddingResponse; + +/** + * This class implements an OData delete request. + */ +public interface ODataReferenceAddingRequest + extends ODataBasicRequest, ODataBatchableRequest { +} diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/response/v4/ODataReferenceAddingResponse.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/response/v4/ODataReferenceAddingResponse.java new file mode 100644 index 000000000..12781f4fb --- /dev/null +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/response/v4/ODataReferenceAddingResponse.java @@ -0,0 +1,29 @@ +/* + * 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.client.api.communication.response.v4; + +import org.apache.olingo.client.api.communication.response.*; + +/** + * This class implements the response to an OData delete request. + * + * @see org.apache.olingo.client.core.communication.request.cud.ODataDeleteRequest + */ +public interface ODataReferenceAddingResponse extends ODataResponse { +} diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/CUDRequestFactoryImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/CUDRequestFactoryImpl.java index 090ec1e5c..2a745c938 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/CUDRequestFactoryImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/CUDRequestFactoryImpl.java @@ -22,7 +22,9 @@ import java.net.URI; import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest; import org.apache.olingo.client.api.communication.request.cud.v4.CUDRequestFactory; +import org.apache.olingo.client.api.communication.request.cud.v4.ODataReferenceAddingRequest; import org.apache.olingo.client.api.communication.request.cud.v4.UpdateType; +import org.apache.olingo.client.api.http.HttpMethod; import org.apache.olingo.client.api.v4.ODataClient; import org.apache.olingo.client.core.communication.request.cud.AbstractCUDRequestFactory; import org.apache.olingo.commons.api.domain.v4.ODataSingleton; @@ -47,4 +49,8 @@ public class CUDRequestFactoryImpl extends AbstractCUDRequestFactory return super.getEntityUpdateRequest(targetURI, type, changes); } + @Override + public ODataReferenceAddingRequest getReferenceAddingRequest(final URI targetURI, final URI reference) { + return new ODataReferenceAddingRequestImpl(client, HttpMethod.POST, targetURI, reference); + } } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/ODataReferenceAddingRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/ODataReferenceAddingRequestImpl.java new file mode 100644 index 000000000..b596be608 --- /dev/null +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/v4/ODataReferenceAddingRequestImpl.java @@ -0,0 +1,92 @@ +/* + * 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.client.core.communication.request.cud.v4; + +import java.io.IOException; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.olingo.client.api.CommonODataClient; +import org.apache.olingo.client.api.http.HttpMethod; +import org.apache.olingo.client.core.communication.request.AbstractODataBasicRequest; +import org.apache.olingo.client.core.communication.response.AbstractODataResponse; +import org.apache.olingo.commons.api.format.ODataFormat; + +import java.io.InputStream; +import java.net.URI; +import org.apache.commons.io.IOUtils; +import org.apache.olingo.client.api.communication.request.cud.v4.ODataReferenceAddingRequest; +import org.apache.olingo.client.api.communication.response.v4.ODataReferenceAddingResponse; +import org.apache.olingo.commons.api.Constants; + +/** + * This class implements an OData delete request. + */ +public class ODataReferenceAddingRequestImpl extends AbstractODataBasicRequest + implements ODataReferenceAddingRequest { + + final URI reference; + + ODataReferenceAddingRequestImpl( + final CommonODataClient odataClient, final HttpMethod method, final URI uri, final URI reference) { + super(odataClient, method, uri); + this.reference = reference; + + + } + + @Override + public ODataFormat getDefaultFormat() { + return odataClient.getConfiguration().getDefaultPubFormat(); + } + + /** + * No payload: null will be returned. + */ + @Override + protected InputStream getPayload() { + if (reference == null) { + return null; + } else { + try { + return IOUtils.toInputStream(reference.toASCIIString(), Constants.UTF8); + } catch (IOException e) { + LOG.warn("Error serializing reference {}", reference); + throw new IllegalArgumentException(e); + } + } + } + + @Override + public ODataReferenceAddingResponse execute() { + return new ODataReferenceAddingResponseImpl(odataClient, httpClient, doExecute()); + } + + /** + * Response class about an ODataDeleteRequest. + */ + private class ODataReferenceAddingResponseImpl extends AbstractODataResponse implements ODataReferenceAddingResponse { + + private ODataReferenceAddingResponseImpl( + final CommonODataClient odataClient, final HttpClient httpClient, final HttpResponse res) { + + super(odataClient, httpClient, res); + this.close(); + } + } +}