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 f1b9eb5ab..b06b594f4 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 @@ -56,6 +56,8 @@ public abstract class AbstractCollectionInvocationHandler referenceItems; + protected Collection newest; + protected final URI baseURI; protected CommonURIBuilder uri; @@ -67,6 +69,8 @@ public abstract class AbstractCollectionInvocationHandler, Object> annotationsByTerm = new HashMap, Object>(); + private boolean changed = false; + public AbstractCollectionInvocationHandler( final AbstractService service, final Collection items, @@ -78,6 +82,7 @@ public abstract class AbstractCollectionInvocationHandler(); + this.newest = new ArrayList(); this.uri = uri; this.baseURI = this.uri == null ? null : this.uri.build(); } @@ -176,6 +181,8 @@ public abstract class AbstractCollectionInvocationHandler collection) { + changed = true; + newest.addAll(collection); return items.addAll(collection); } @@ -325,4 +335,8 @@ public abstract class AbstractCollectionInvocationHandler toBeLinked = new HashSet(); - for (Object proxy : type == ODataLinkType.ENTITY_SET_NAVIGATION ? (Collection) property.getValue() : Collections.singleton(property.getValue())) { final EntityInvocationHandler target = (EntityInvocationHandler) Proxy.getInvocationHandler(proxy); - final AttachedEntityStatus status; - if (!service.getContext().entityContext().isAttached(target)) { - status = resolveNavigationLink(property.getKey(), target); - } else { - status = service.getContext().entityContext().getStatus(target); - } - - LOG.debug("Found link to '{}({})'", target, status); - - final URI editLink = target.getEntity().getEditLink(); - - if ((status == AttachedEntityStatus.ATTACHED || status == AttachedEntityStatus.LINKED) && !target.isChanged()) { - LOG.debug("Add link to '{}'", target); - entity.addLink(buildNavigationLink( - property.getKey().name(), - URIUtils.getURI(service.getClient().getServiceRoot(), editLink.toASCIIString()), type)); - } else { - if (!items.contains(target)) { - pos = processEntityContext(target, pos, items, delayedUpdates, changeset); - pos++; - } - - final Integer targetPos = items.get(target); - if (targetPos == null) { - // schedule update for the current object - LOG.debug("Schedule '{}' from '{}' to '{}'", type.name(), handler, target); - toBeLinked.add(target); - } else if (status == AttachedEntityStatus.CHANGED) { - LOG.debug("Changed: '{}' from '{}' to (${}) '{}'", type.name(), handler, targetPos, target); - entity.addLink(buildNavigationLink( - property.getKey().name(), - URIUtils.getURI(service.getClient().getServiceRoot(), editLink.toASCIIString()), type)); - } else { - // create the link for the current object - LOG.debug("'{}' from '{}' to (${}) '{}'", type.name(), handler, targetPos, target); - - entity.addLink(buildNavigationLink(property.getKey().name(), URI.create("$" + targetPos), type)); - } - } + toBeLinked.addAll(processLinkChanges( + handler, target, property.getKey(), type, pos, items, delayedUpdates, changeset)); } if (!toBeLinked.isEmpty()) { @@ -236,6 +198,41 @@ abstract class AbstractPersistenceManager implements PersistenceManager { } } + // Required by linking provided on existent object. Say: + // container.getCustomers().getByKey(1).getOrders().add(order) + // Required by linking provided via entity reference ID. Say: + // container.getCustomers().getByKey(1).getOrders().addRef(order) + for (Map.Entry property : handler.linkCache.entrySet()) { + if (property.getValue() instanceof Proxy) { + final InvocationHandler target = Proxy.getInvocationHandler(property.getValue()); + + if (target instanceof EntityCollectionInvocationHandler + && ((EntityCollectionInvocationHandler) target).isChanged()) { + + final ODataLinkType type = Collection.class.isAssignableFrom(property.getValue().getClass()) + ? ODataLinkType.ENTITY_SET_NAVIGATION + : ODataLinkType.ENTITY_NAVIGATION; + + final Set toBeLinked = new HashSet(); + + for (Object proxy : ((EntityCollectionInvocationHandler) target).newest) { + final EntityInvocationHandler targetEntity = (EntityInvocationHandler) Proxy.getInvocationHandler(proxy); + + toBeLinked.addAll(processLinkChanges( + handler, targetEntity, property.getKey(), type, pos, items, delayedUpdates, changeset)); + } + + if (!toBeLinked.isEmpty()) { + delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, toBeLinked, type)); + } + + for (String ref : ((EntityCollectionInvocationHandler) target).referenceItems) { + delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, ref)); + } + } + } + } + if (entity instanceof ODataEntity) { for (Map.Entry entry : handler.getNavPropAnnotatableHandlers().entrySet()) { @@ -305,6 +302,62 @@ abstract class AbstractPersistenceManager implements PersistenceManager { return pos; } + protected Set processLinkChanges( + final EntityInvocationHandler source, + final EntityInvocationHandler target, + final NavigationProperty property, + final ODataLinkType type, + int pos, + final TransactionItems items, + final List delayedUpdates, + final PersistenceChanges changeset) { + + final Set toBeLinked = new HashSet(); + + final AttachedEntityStatus status; + if (!service.getContext().entityContext().isAttached(target)) { + status = resolveNavigationLink(property, target); + } else { + status = service.getContext().entityContext().getStatus(target); + } + + LOG.debug("Found link to '{}({})'", target, status); + + final URI editLink = target.getEntity().getEditLink(); + + if ((status == AttachedEntityStatus.ATTACHED || status == AttachedEntityStatus.LINKED) && !target.isChanged()) { + LOG.debug("Add link to '{}'", target); + source.getEntity().addLink(buildNavigationLink( + property.name(), + URIUtils.getURI(service.getClient().getServiceRoot(), editLink.toASCIIString()), type)); + } else { + if (!items.contains(target)) { + pos = processEntityContext(target, pos, items, delayedUpdates, changeset); + pos++; + } + + final Integer targetPos = items.get(target); + if (targetPos == null) { + // schedule update for the current object + LOG.debug("Schedule '{}' from '{}' to '{}'", type.name(), source, target); + toBeLinked.add(target); + } else if (status == AttachedEntityStatus.CHANGED) { + LOG.debug("Changed: '{}' from '{}' to (${}) '{}'", type.name(), source, targetPos, target); + source.getEntity().addLink(buildNavigationLink( + property.name(), + URIUtils.getURI(service.getClient().getServiceRoot(), editLink.toASCIIString()), type)); + } else { + // create the link for the current object + LOG.debug("'{}' from '{}' to (${}) '{}'", type.name(), source, targetPos, target); + + source.getEntity().addLink( + buildNavigationLink(property.name(), URI.create("$" + targetPos), type)); + } + } + + return toBeLinked; + } + protected void processDelayedUpdates( final List delayedUpdates, int pos, 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 054604bb9..51d62adee 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 @@ -294,10 +294,21 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler return isChanged(true); } - public boolean isChanged(final boolean includeMedia) { + public boolean isChanged(final boolean deep) { + boolean linkedChanges = false; + for (Map.Entry link : linkCache.entrySet()) { + final InvocationHandler handler = Proxy.getInvocationHandler(link.getValue()); + if (handler instanceof EntityInvocationHandler) { + linkedChanges = linkedChanges || ((EntityInvocationHandler) handler).isChanged(); + } else if (handler instanceof EntityCollectionInvocationHandler) { + linkedChanges = linkedChanges || ((EntityCollectionInvocationHandler) handler).isChanged(); + } + } + return this.linkChanges.hashCode() != this.linksTag || this.propertyChanges.hashCode() != this.propertiesTag - || (includeMedia && (this.stream != null + || (deep && (linkedChanges + || this.stream != null || !this.streamedPropertyChanges.isEmpty())); } @@ -355,6 +366,7 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler if (navPropValue != null) { cacheLink(property, navPropValue); + attach(); } return navPropValue; 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 15076d035..6db031716 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 @@ -115,6 +115,13 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase { container.getCustomers().getByKey(1).setOrders(orders); container.flush(); } + + @Test + public void addViaReference2() { + final Order order = container.getOrders().getByKey(8).load(); + container.getCustomers().getByKey(1).getOrders().addRef(order); + container.flush(); + } @Test public void readAndCheckForPrimitive() {