Skip to content

Commit fca2493

Browse files
authored
Merge pull request #1142 from microsoft/andrueastman/backingstoreNesting
Fixes for nested IBackedModel properties in the InMemoryBackingStore
2 parents c5044fc + 71c4853 commit fca2493

File tree

6 files changed

+599
-15
lines changed

6 files changed

+599
-15
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Changed
1313

14+
## [1.1.2] - 2024-03-26
15+
16+
### Changed
17+
18+
- Fixes a bug in the InMemoryBackingStore that would not leave out properties in nested IBackedModel properties.
19+
1420
## [1.1.1] - 2024-03-20
1521

1622
### Changed

components/abstractions/spotBugsExcludeFilter.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,12 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu
5050
<Bug pattern="EI_EXPOSE_REP" />
5151
<Class name="com.microsoft.kiota.serialization.mocks.TestEntity" />
5252
</Match>
53+
<Match>
54+
<Bug pattern="EI_EXPOSE_REP" />
55+
<Class name="com.microsoft.kiota.TestEntity" />
56+
</Match>
57+
<Match>
58+
<Bug pattern="NP_LOAD_OF_KNOWN_NULL_VALUE" />
59+
<Class name="com.microsoft.kiota.store.InMemoryBackingStore" />
60+
</Match>
5361
</FindBugsFilter>

components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import jakarta.annotation.Nullable;
77

88
import java.util.ArrayList;
9+
import java.util.Collection;
910
import java.util.HashMap;
1011
import java.util.List;
1112
import java.util.Map;
@@ -33,15 +34,15 @@ public A getValue0() {
3334
}
3435

3536
public Pair<A, B> setValue0(A value0) {
36-
return new Pair(value0, value1);
37+
return new Pair<>(value0, value1);
3738
}
3839

3940
public B getValue1() {
4041
return value1;
4142
}
4243

4344
public Pair<A, B> setValue1(B value1) {
44-
return new Pair(value0, value1);
45+
return new Pair<>(value0, value1);
4546
}
4647
}
4748

@@ -55,7 +56,15 @@ public void setIsInitializationCompleted(final boolean value) {
5556
this.isInitializationCompleted = value;
5657
for (final Map.Entry<String, Pair<Boolean, Object>> entry : this.store.entrySet()) {
5758
final Pair<Boolean, Object> wrapper = entry.getValue();
58-
final Pair<Boolean, Object> updatedValue = wrapper.setValue0(Boolean.valueOf(!value));
59+
if (wrapper.getValue1() instanceof BackedModel) {
60+
BackedModel backedModel = (BackedModel) wrapper.getValue1();
61+
backedModel
62+
.getBackingStore()
63+
.setIsInitializationCompleted(value); // propagate initialization
64+
}
65+
ensureCollectionPropertyIsConsistent(
66+
entry.getKey(), this.store.get(entry.getKey()).getValue1());
67+
final Pair<Boolean, Object> updatedValue = wrapper.setValue0(!value);
5968
entry.setValue(updatedValue);
6069
}
6170
}
@@ -80,10 +89,12 @@ public void clear() {
8089
final Map<String, Object> result = new HashMap<>();
8190
for (final Map.Entry<String, Pair<Boolean, Object>> entry : this.store.entrySet()) {
8291
final Pair<Boolean, Object> wrapper = entry.getValue();
83-
final Object value = this.getValueFromWrapper(wrapper);
92+
final Object value = this.getValueFromWrapper(entry.getKey(), wrapper);
8493

8594
if (value != null) {
8695
result.put(entry.getKey(), wrapper.getValue1());
96+
} else if (Boolean.TRUE.equals(wrapper.getValue0())) {
97+
result.put(entry.getKey(), null);
8798
}
8899
}
89100
return result;
@@ -101,13 +112,15 @@ public void clear() {
101112
return result;
102113
}
103114

104-
private Object getValueFromWrapper(final Pair<Boolean, Object> wrapper) {
115+
private Object getValueFromWrapper(final String entryKey, final Pair<Boolean, Object> wrapper) {
105116
if (wrapper != null) {
106117
final Boolean hasChanged = wrapper.getValue0();
107-
if (!this.returnOnlyChangedValues
108-
|| (this.returnOnlyChangedValues
109-
&& hasChanged != null
110-
&& hasChanged.booleanValue())) {
118+
if (!this.returnOnlyChangedValues || Boolean.TRUE.equals(hasChanged)) {
119+
ensureCollectionPropertyIsConsistent(entryKey, wrapper.getValue1());
120+
if (wrapper.getValue1() instanceof Pair) {
121+
Pair<?, ?> collectionTuple = (Pair<?, ?>) wrapper.getValue1();
122+
return collectionTuple.getValue0();
123+
}
111124
return wrapper.getValue1();
112125
}
113126
}
@@ -118,7 +131,7 @@ private Object getValueFromWrapper(final Pair<Boolean, Object> wrapper) {
118131
@Nullable public <T> T get(@Nonnull final String key) {
119132
Objects.requireNonNull(key);
120133
final Pair<Boolean, Object> wrapper = this.store.get(key);
121-
final Object value = this.getValueFromWrapper(wrapper);
134+
final Object value = this.getValueFromWrapper(key, wrapper);
122135
try {
123136
return (T) value;
124137
} catch (ClassCastException ex) {
@@ -128,11 +141,39 @@ private Object getValueFromWrapper(final Pair<Boolean, Object> wrapper) {
128141

129142
public <T> void set(@Nonnull final String key, @Nullable final T value) {
130143
Objects.requireNonNull(key);
131-
final Pair<Boolean, Object> valueToAdd =
132-
new Pair(Boolean.valueOf(this.isInitializationCompleted), value);
144+
Pair<Boolean, Object> valueToAdd = new Pair<>(this.isInitializationCompleted, value);
145+
if (value instanceof Collection) {
146+
valueToAdd = valueToAdd.setValue1(new Pair<>(value, ((Collection<?>) value).size()));
147+
final Collection<Object> items = (Collection<Object>) value;
148+
setupNestedSubscriptions(items, key, value);
149+
} else if (value instanceof Map) {
150+
valueToAdd = valueToAdd.setValue1(new Pair<>(value, ((Map<?, ?>) value).size()));
151+
final Map<?, Object> items = (Map<?, Object>) value;
152+
setupNestedSubscriptions(items.values(), key, value);
153+
} else if (value instanceof BackedModel) {
154+
final BackedModel backedModel = (BackedModel) value;
155+
backedModel
156+
.getBackingStore()
157+
.subscribe(
158+
key,
159+
(keyString, oldObject, newObject) -> {
160+
backedModel
161+
.getBackingStore()
162+
.setIsInitializationCompleted(
163+
false); // All its properties are dirty as the model
164+
// has been touched.
165+
set(key, value);
166+
}); // use property name(key) as subscriptionId to prevent excess
167+
// subscription creation in the event this is called again
168+
}
169+
133170
final Pair<Boolean, Object> oldValue = this.store.put(key, valueToAdd);
134171
for (final TriConsumer<String, Object, Object> callback : this.subscriptionStore.values()) {
135-
callback.accept(key, oldValue.getValue1(), value);
172+
if (oldValue != null) {
173+
callback.accept(key, oldValue.getValue1(), value);
174+
} else {
175+
callback.accept(key, null, value);
176+
}
136177
}
137178
}
138179

@@ -154,4 +195,53 @@ public void subscribe(
154195
Objects.requireNonNull(subscriptionId);
155196
this.subscriptionStore.put(subscriptionId, callback);
156197
}
198+
199+
private void setupNestedSubscriptions(
200+
final Collection<Object> items, final String key, final Object value) {
201+
for (final Object item : items) {
202+
if (item instanceof BackedModel) {
203+
final BackedModel backedModel = (BackedModel) item;
204+
backedModel.getBackingStore().setIsInitializationCompleted(false);
205+
backedModel
206+
.getBackingStore()
207+
.subscribe(key, (keyString, oldObject, newObject) -> set(key, value));
208+
}
209+
}
210+
}
211+
212+
private void ensureCollectionPropertyIsConsistent(final String key, final Object storeItem) {
213+
if (storeItem instanceof Pair) { // check if we put in a collection annotated with the size
214+
final Pair<?, Integer> collectionTuple = (Pair<?, Integer>) storeItem;
215+
Object[] items;
216+
if (collectionTuple.getValue0() instanceof Collection) {
217+
items = ((Collection<Object>) collectionTuple.getValue0()).toArray();
218+
} else { // it is a map
219+
items = ((Map<?, Object>) collectionTuple.getValue0()).values().toArray();
220+
}
221+
222+
for (final Object item : items) {
223+
touchNestedProperties(item); // call get on nested properties
224+
}
225+
226+
if (collectionTuple.getValue1()
227+
!= items.length) { // and the size has changed since we last updated
228+
set(
229+
key,
230+
collectionTuple.getValue0()); // ensure the store is notified the collection
231+
// property is "dirty"
232+
}
233+
}
234+
touchNestedProperties(storeItem); // call get on nested properties
235+
}
236+
237+
private void touchNestedProperties(final Object nestedObject) {
238+
if (nestedObject instanceof BackedModel) {
239+
// Call Get<>() on nested properties so that this method may be called recursively to
240+
// ensure collections are consistent
241+
final BackedModel backedModel = (BackedModel) nestedObject;
242+
for (final String itemKey : backedModel.getBackingStore().enumerate().keySet()) {
243+
backedModel.getBackingStore().get(itemKey);
244+
}
245+
}
246+
}
157247
}

components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,101 @@
11
package com.microsoft.kiota;
22

3+
import com.microsoft.kiota.serialization.AdditionalDataHolder;
34
import com.microsoft.kiota.serialization.Parsable;
45
import com.microsoft.kiota.serialization.ParseNode;
56
import com.microsoft.kiota.serialization.SerializationWriter;
7+
import com.microsoft.kiota.store.BackedModel;
8+
import com.microsoft.kiota.store.BackingStore;
9+
import com.microsoft.kiota.store.BackingStoreFactorySingleton;
610

711
import jakarta.annotation.Nonnull;
812

913
import java.util.HashMap;
14+
import java.util.List;
1015
import java.util.Map;
1116
import java.util.function.Consumer;
1217

13-
public class TestEntity implements Parsable {
18+
public class TestEntity implements Parsable, AdditionalDataHolder, BackedModel {
19+
20+
protected BackingStore backingStore;
21+
22+
/**
23+
* Gets the backingStore property value. Stores model information.
24+
* @return a {@link BackingStore}
25+
*/
26+
@jakarta.annotation.Nonnull public BackingStore getBackingStore() {
27+
return this.backingStore;
28+
}
29+
30+
public String getOdataType() {
31+
return this.backingStore.get("@odata.type");
32+
}
33+
34+
public void setOdataType(String odataType) {
35+
this.backingStore.set("@odata.type", odataType);
36+
}
37+
38+
public String getId() {
39+
return this.backingStore.get("id");
40+
}
41+
42+
public void setId(String _id) {
43+
this.backingStore.set("id", _id);
44+
}
45+
46+
public List<String> getBusinessPhones() {
47+
return this.backingStore.get("businessPhones");
48+
}
49+
50+
public void setBusinessPhones(List<String> _businessPhones) {
51+
this.backingStore.set("businessPhones", _businessPhones);
52+
}
53+
54+
public TestEntity getManager() {
55+
return this.backingStore.get("manager");
56+
}
57+
58+
public void setManager(TestEntity manager) {
59+
this.backingStore.set("manager", manager);
60+
}
61+
62+
public List<TestEntity> getColleagues() {
63+
return this.backingStore.get("colleagues");
64+
}
65+
66+
public void setColleagues(List<TestEntity> colleagues) {
67+
this.backingStore.set("colleagues", colleagues);
68+
}
69+
70+
/**
71+
* Instantiates a new {@link TestEntity} and sets the default values.
72+
*/
73+
public TestEntity() {
74+
this.backingStore = BackingStoreFactorySingleton.instance.createBackingStore();
75+
this.setAdditionalData(new HashMap<>());
76+
this.setOdataType("#microsoft.graph.testEntity");
77+
}
78+
79+
/**
80+
* Gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
81+
* @return a {@link Map<String, Object>}
82+
*/
83+
@jakarta.annotation.Nonnull public Map<String, Object> getAdditionalData() {
84+
Map<String, Object> value = this.backingStore.get("additionalData");
85+
if (value == null) {
86+
value = new HashMap<>();
87+
this.setAdditionalData(value);
88+
}
89+
return value;
90+
}
91+
92+
/**
93+
* Sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
94+
* @param value Value to set for the AdditionalData property.
95+
*/
96+
public void setAdditionalData(@jakarta.annotation.Nullable final Map<String, Object> value) {
97+
this.backingStore.set("additionalData", value);
98+
}
1499

15100
@Override
16101
@Nonnull public Map<String, Consumer<ParseNode>> getFieldDeserializers() {

0 commit comments

Comments
 (0)