OLINGO-848: getting a property of an entity flags the entity as changed

Signed-off-by: Christian Amend <christian.amend@sap.com>
This commit is contained in:
Frederik Zimmer 2016-02-26 17:36:13 +01:00 committed by Christian Amend
parent 40ff7be0f3
commit c7e6630492
7 changed files with 305 additions and 61 deletions

View File

@ -64,8 +64,6 @@ public abstract class AbstractCollectionInvocationHandler<T extends Serializable
private final Map<Class<? extends AbstractTerm>, Object> annotationsByTerm =
new HashMap<Class<? extends AbstractTerm>, Object>();
private boolean changed = false;
public AbstractCollectionInvocationHandler(
final AbstractService<?> service,
final Collection<T> items,
@ -174,7 +172,6 @@ public abstract class AbstractCollectionInvocationHandler<T extends Serializable
service.getContext().entityContext().attachNew(handler);
}
}
changed = true;
return items.add(element);
}
@ -186,7 +183,6 @@ public abstract class AbstractCollectionInvocationHandler<T extends Serializable
return false;
}
changed = true;
return referenceItems.add(id.toASCIIString());
}
@ -243,7 +239,6 @@ public abstract class AbstractCollectionInvocationHandler<T extends Serializable
@Override
public boolean addAll(final Collection<? extends T> collection) {
changed = true;
return items.addAll(collection);
}
@ -324,8 +319,4 @@ public abstract class AbstractCollectionInvocationHandler<T extends Serializable
this.uri = this.baseURI == null ? null : getClient().newURIBuilder(baseURI.toASCIIString());
this.nextPageURI = null;
}
public boolean isChanged() {
return changed;
}
}

View File

@ -93,10 +93,6 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
protected final Map<NavigationProperty, Object> linkCache = new HashMap<NavigationProperty, Object>();
protected int propertiesTag = 0;
protected int linksTag = 0;
protected final Map<String, EdmStreamValue> streamedPropertyChanges = new HashMap<String, EdmStreamValue>();
protected final Map<String, EdmStreamValue> streamedPropertyCache = new HashMap<String, EdmStreamValue>();
@ -386,7 +382,6 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
}
if (res != null) {
addPropertyChanges(name, res);
propertyCache.put(name, res);
}
@ -526,7 +521,79 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
}
public Map<String, Object> getPropertyChanges() {
return propertyChanges;
Map<String, Object> changedProperties = new HashMap<String, Object>();
changedProperties.putAll(propertyChanges);
for (Map.Entry<String, Object> propertyCacheEntry : propertyCache.entrySet()) {
if (hasCachedPropertyChanged(propertyCacheEntry.getValue())) {
changedProperties.put(propertyCacheEntry.getKey(), propertyCacheEntry.getValue());
}
}
return changedProperties;
}
protected boolean hasCachedPropertyChanged(final Object cachedValue) {
AbstractStructuredInvocationHandler structuredInvocationHandler = getStructuredInvocationHandler(cachedValue);
if (structuredInvocationHandler != null) {
return structuredInvocationHandler.isChanged();
}
return false;
}
public boolean isChanged() {
return !linkChanges.isEmpty()
|| hasPropertyChanges();
}
protected boolean hasPropertyChanges() {
return !propertyChanges.isEmpty() || hasDeepPropertyChanges();
}
protected boolean hasDeepPropertyChanges() {
for (Object propertyValue : propertyCache.values()) {
if (hasCachedPropertyChanged(propertyValue)) {
return true;
}
}
return false;
}
public void applyChanges() {
streamedPropertyCache.putAll(streamedPropertyChanges);
streamedPropertyChanges.clear();
propertyCache.putAll(propertyChanges);
propertyChanges.clear();
linkCache.putAll(linkChanges);
linkChanges.clear();
applyChangesOnChildren();
}
protected void applyChangesOnChildren() {
for (Object propertyValue : propertyCache.values()) {
applyChanges(propertyValue);
}
}
protected void applyChanges(final Object cachedValue) {
AbstractStructuredInvocationHandler structuredInvocationHandler = getStructuredInvocationHandler(cachedValue);
if (structuredInvocationHandler != null) {
structuredInvocationHandler.applyChanges();
}
}
protected AbstractStructuredInvocationHandler getStructuredInvocationHandler(final Object value) {
if (value != null && Proxy.isProxyClass(value.getClass())) {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(value);
if (invocationHandler instanceof AbstractStructuredInvocationHandler) {
return (AbstractStructuredInvocationHandler) invocationHandler;
}
}
return null;
}
public Collection<String> readAdditionalPropertyNames() {
@ -567,14 +634,11 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
}
protected void addPropertyChanges(final String name, final Object value) {
final int checkpoint = propertyChanges.hashCode();
updatePropertiesTag(checkpoint);
propertyCache.remove(name);
propertyChanges.put(name, value);
}
protected void addLinkChanges(final NavigationProperty navProp, final Object value) {
final int checkpoint = linkChanges.hashCode();
updateLinksTag(checkpoint);
linkChanges.put(navProp, value);
if (linkCache.containsKey(navProp)) {
@ -582,18 +646,6 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
}
}
protected void updatePropertiesTag(final int checkpoint) {
if (propertiesTag == 0 || checkpoint == propertiesTag) {
propertiesTag = propertyChanges.hashCode();
}
}
protected void updateLinksTag(final int checkpoint) {
if (linksTag == 0 || checkpoint == linksTag) {
linksTag = linkChanges.hashCode();
}
}
public Map<String, EdmStreamValue> getStreamedPropertyChanges() {
return streamedPropertyChanges;
}
@ -642,8 +694,6 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
protected abstract void load();
public abstract boolean isChanged();
protected abstract <T extends ClientProperty> List<T> getInternalProperties();
protected abstract ClientProperty getInternalProperty(final String name);

View File

@ -27,10 +27,10 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.olingo.client.api.communication.request.retrieve.ODataPropertyRequest;
import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
import org.apache.olingo.client.api.uri.URIBuilder;
import org.apache.olingo.client.api.domain.ClientComplexValue;
import org.apache.olingo.client.api.domain.ClientLinked;
import org.apache.olingo.client.api.domain.ClientProperty;
import org.apache.olingo.client.api.uri.URIBuilder;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.ext.proxy.AbstractService;
import org.apache.olingo.ext.proxy.api.annotations.ComplexType;
@ -144,11 +144,6 @@ public class ComplexInvocationHandler extends AbstractStructuredInvocationHandle
return retrieveNavigationProperty(property, getter);
}
@Override
public boolean isChanged() {
return getEntityHandler() == null ? false : getEntityHandler().isChanged();
}
@Override
protected void load() {
try {

View File

@ -229,10 +229,9 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler
this.streamedPropertyChanges.clear();
this.streamedPropertyCache.clear();
this.propertyChanges.clear();
this.propertyCache.clear();
this.linkChanges.clear();
this.linkCache.clear();
this.propertiesTag = 0;
this.linksTag = 0;
this.annotations.clear();
}
@ -301,11 +300,10 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler
return isChanged(true);
}
public boolean isChanged(final boolean deep) {
return this.linkChanges.hashCode() != this.linksTag
|| this.propertyChanges.hashCode() != this.propertiesTag
|| (deep && (this.stream != null
|| !this.streamedPropertyChanges.isEmpty()));
public boolean isChanged(final boolean considerStreamProperties) {
return super.isChanged()
|| (considerStreamProperties && (stream != null
|| !streamedPropertyChanges.isEmpty()));
}
public void uploadStream(final EdmStreamValue stream) {

View File

@ -74,15 +74,27 @@ public class NonTransactionalPersistenceManagerImpl extends AbstractPersistenceM
}
if (entry.getValue() != null
&& response instanceof ODataEntityCreateResponse && response.getStatusCode() == 201) {
&& response instanceof ODataEntityCreateResponse && (response.getStatusCode() == 201 || response
.getStatusCode() == 204)) {
if (response.getStatusCode() == 201) {
entry.getValue().setEntity(((ODataEntityCreateResponse<?>) response).getBody());
responses.put(index, entry.getValue().getEntityURI());
LOG.debug("Upgrade created object '{}'", entry.getValue());
} else {
entry.getValue().applyChanges();
responses.put(index, null);
}
} else if (entry.getValue() != null
&& response instanceof ODataEntityUpdateResponse && response.getStatusCode() == 200) {
&& response instanceof ODataEntityUpdateResponse && (response.getStatusCode() == 200 || response
.getStatusCode() == 204)) {
if (response.getStatusCode() == 200) {
entry.getValue().setEntity(((ODataEntityUpdateResponse<?>) response).getBody());
responses.put(index, entry.getValue().getEntityURI());
LOG.debug("Upgrade updated object '{}'", entry.getValue());
} else {
entry.getValue().applyChanges();
responses.put(index, null);
}
} else {
responses.put(index, null);
}

View File

@ -116,12 +116,22 @@ public class TransactionalPersistenceManagerImpl extends AbstractPersistenceMana
final EntityInvocationHandler handler = items.get(changesetItemId);
if (handler != null) {
if (res instanceof ODataEntityCreateResponse && res.getStatusCode() == 201) {
if (res instanceof ODataEntityCreateResponse && (res.getStatusCode() == 201 || res
.getStatusCode() == 204)) {
if (res.getStatusCode() == 201) {
handler.setEntity(((ODataEntityCreateResponse<?>) res).getBody());
LOG.debug("Upgrade created object '{}'", handler);
} else if (res instanceof ODataEntityUpdateResponse && res.getStatusCode() == 200) {
} else {
handler.applyChanges();
}
} else if (res instanceof ODataEntityUpdateResponse && (res.getStatusCode() == 200 || res
.getStatusCode() == 204)) {
if (res.getStatusCode() == 201) {
handler.setEntity(((ODataEntityUpdateResponse<?>) res).getBody());
LOG.debug("Upgrade updated object '{}'", handler);
} else {
handler.applyChanges();
}
}
}
}

View File

@ -0,0 +1,188 @@
/*
* 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.proxy;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Proxy;
import java.sql.Timestamp;
import java.util.Calendar;
import org.apache.commons.lang3.RandomUtils;
import org.apache.olingo.ext.proxy.api.ComplexType;
import org.apache.olingo.ext.proxy.api.EntityCollection;
import org.apache.olingo.ext.proxy.api.EntityType;
import org.apache.olingo.ext.proxy.commons.ComplexInvocationHandler;
import org.apache.olingo.ext.proxy.commons.EntityCollectionInvocationHandler;
import org.apache.olingo.ext.proxy.commons.EntityInvocationHandler;
// CHECKSTYLE:OFF (Maven checkstyle)
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.InMemoryEntities;
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.types.Account;
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.types.Address;
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.types.Customer;
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.types.Order;
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.types.OrderCollection;
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.types.PaymentInstrument;
import org.apache.olingo.fit.proxy.staticservice.microsoft.test.odata.services.odatawcfservice.types.PaymentInstrumentCollection;
// CHECKSTYLE:ON (Maven checkstyle)
import org.junit.Test;
public class ChangeDetectionTestITCase extends AbstractTestITCase {
@Test
public void entityUnchangedOnGetProperty() {
final Customer customer = getContainer().getCustomers().getByKey(1).load();
assertFalse(isChanged(customer));
customer.getLastName();
assertFalse(isChanged(customer));
}
@Test
public void entityChangedOnSetProperty() {
final Customer customer = getContainer().getCustomers().getByKey(1).load();
assertFalse(isChanged(customer));
customer.setLastName("Test");
assertTrue(isChanged(customer));
getContainer().flush();
assertFalse(isChanged(customer));
}
@Test
public void entityUnchangedOnGetComplexProperty() {
final Customer customer = getContainer().getCustomers().getByKey(1).load();
assertFalse(isChanged(customer));
final Address homeAddress = customer.getHomeAddress();
assertFalse(isChanged(customer));
homeAddress.getCity();
assertFalse(isChanged(homeAddress));
assertFalse(isChanged(customer));
}
@Test
public void entityChangedOnSetComplexProperty() {
final Customer customer = getContainer().getCustomers().getByKey(2).load();
assertFalse(isChanged(customer));
final Address newAdress = getContainer().newComplexInstance(Address.class);
customer.setHomeAddress(newAdress);
assertTrue(isChanged(customer));
getContainer().flush();
assertFalse(isChanged(customer));
}
@Test
public void entityChangedOnSetPropertyOfComplexProperty() {
final Customer customer = getContainer().getCustomers().getByKey(1).load();
assertFalse(isChanged(customer));
final Address homeAddress = customer.getHomeAddress();
homeAddress.setCity("Test");
assertTrue(isChanged(customer));
getContainer().flush();
assertFalse(isChanged(customer));
}
@Test
public void entityUnchangedOnGetNavigationProperty() {
final Customer customer = getContainer().getCustomers().getByKey(1).load();
assertFalse(isChanged(customer));
customer.getOrders();
assertFalse(isChanged(customer));
}
@Test
public void entityChangedOnAddNavigationProperty() {
final Account account = getContainer().getAccounts().getByKey(101).load();
assertFalse(isChanged(account));
final PaymentInstrumentCollection instruments = account.getMyPaymentInstruments().execute();
assertFalse(isChanged(account));
final PaymentInstrument instrument = getContainer().newEntityInstance(PaymentInstrument.class);
final int id = RandomUtils.nextInt(101999, 105000);
instrument.setPaymentInstrumentID(id);
instrument.setFriendlyName("New one");
instrument.setCreatedDate(new Timestamp(Calendar.getInstance().getTimeInMillis()));
instruments.add(instrument);
assertTrue(isChanged(instrument));
assertFalse(isChanged(account));
getContainer().flush();
assertFalse(isChanged(instrument));
}
@Test
public void entityCollectionUnchangedOnGet() {
final Customer customer = getContainer().getCustomers().getByKey(1).load();
assertFalse(isChanged(customer));
final OrderCollection orders = customer.getOrders().execute();
assertFalse(isChanged(customer));
for (Order order : orders) {
assertFalse(isChanged(order));
order.getOrderDate();
assertFalse(isChanged(order));
}
assertFalse(isChanged(customer));
}
protected InMemoryEntities getContainer() {
return container;
}
protected boolean isChanged(final EntityType<?> entity) {
EntityInvocationHandler invocationHandler = getInvocationHandler(entity);
return invocationHandler.isChanged();
}
protected boolean isChanged(final ComplexType<?> complex) {
ComplexInvocationHandler invocationHandler = getInvocationHandler(complex);
return invocationHandler.isChanged();
}
protected EntityInvocationHandler getInvocationHandler(final EntityType<?> entity) {
return (EntityInvocationHandler) Proxy.getInvocationHandler(entity);
}
protected ComplexInvocationHandler getInvocationHandler(final ComplexType<?> complex) {
return (ComplexInvocationHandler) Proxy.getInvocationHandler(complex);
}
protected EntityCollectionInvocationHandler<?> getInvocationHandler(
final EntityCollection<?, ?, ?> complex) {
return (EntityCollectionInvocationHandler<?>) Proxy.getInvocationHandler(complex);
}
}