Skip to content

Commit 10f29b8

Browse files
Added tests for lazy handling
1 parent 6e1c20a commit 10f29b8

9 files changed

Lines changed: 311 additions & 31 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright © 2023 XDEV Software (https://xdev.software)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package software.xdev.spring.data.eclipse.store.exceptions;
17+
18+
public class LazyNotUnlinkableException extends RuntimeException
19+
{
20+
public LazyNotUnlinkableException(final String message, final Throwable e)
21+
{
22+
super(message, e);
23+
}
24+
}

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/access/AccessHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public static Field getInheritedPrivateField(final Class<?> clazz, final String
9999
*/
100100
public static <T> Object readFieldVariable(final Field field, final T sourceObject)
101101
{
102-
try(final FieldAccessModifier<T> fieldAccessModifier = FieldAccessModifier.makeFieldReadable(
102+
try(final FieldAccessModifier<T> fieldAccessModifier = FieldAccessModifier.prepareForField(
103103
Objects.requireNonNull(field),
104104
Objects.requireNonNull(sourceObject)))
105105
{

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/access/modifier/FieldAccessModifier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*/
2525
public interface FieldAccessModifier<E> extends AutoCloseable
2626
{
27-
static <T> FieldAccessModifier<T> makeFieldReadable(final Field field, final T sourceObject)
27+
static <T> FieldAccessModifier<T> prepareForField(final Field field, final T sourceObject)
2828
{
2929
return new FieldAccessibleMaker<>(Objects.requireNonNull(field), Objects.requireNonNull(sourceObject));
3030
}

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/lazy/SpringDataEclipseStoreLazy.java

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
*/
1616
package software.xdev.spring.data.eclipse.store.repository.lazy;
1717

18+
import java.lang.reflect.Field;
1819
import java.util.Objects;
1920

2021
import org.eclipse.serializer.reference.Lazy;
2122
import org.eclipse.serializer.reference.ObjectSwizzling;
2223
import org.eclipse.serializer.reference.Swizzling;
2324

25+
import software.xdev.spring.data.eclipse.store.exceptions.LazyNotUnlinkableException;
26+
import software.xdev.spring.data.eclipse.store.repository.access.modifier.FieldAccessModifier;
27+
2428

2529
/**
2630
* This is the Lazy-Wrapper a user of the Spring-Data-Eclipse-Store-Library should use. Please <b>do not use the
@@ -35,10 +39,22 @@ static <T> SpringDataEclipseStoreLazy.Default<T> build(final T objectToWrapInLaz
3539
return new Default<>(objectToWrapInLazy);
3640
}
3741

38-
SpringDataEclipseStoreLazy<T> copy();
42+
SpringDataEclipseStoreLazy<T> copyOnlyWithReference();
43+
44+
void unlink();
3945

4046
long objectId();
4147

48+
boolean isOriginalObject();
49+
50+
interface Internals
51+
{
52+
static <T> SpringDataEclipseStoreLazy.Default<T> build(final Lazy<T> lazySubject)
53+
{
54+
return new Default<>(lazySubject);
55+
}
56+
}
57+
4258
/**
4359
* This class is very complex and its various membervariables all have their reason to exist. This code is very
4460
* difficult to read due to its the functionality explained in the {@link SpringDataEclipseStoreLazyBinaryHandler}.
@@ -53,28 +69,44 @@ class Default<T> implements SpringDataEclipseStoreLazy<T>
5369
private transient ObjectSwizzling loader;
5470
private transient boolean isStored = false;
5571

56-
private Default(final T wrappedObject)
72+
private Default(final Lazy<T> lazySubject)
5773
{
58-
this.objectToBeWrapped = wrappedObject;
74+
this.setWrappedLazy(lazySubject);
75+
}
76+
77+
private Default(final T objectToBeWrapped)
78+
{
79+
this.objectToBeWrapped = objectToBeWrapped;
5980
}
6081

6182
private Default(final long objectId, final ObjectSwizzling loader)
6283
{
6384
this.objectId = objectId;
6485
this.loader = loader;
86+
// This object is already stored in the real storage. So it can get cleared.
87+
this.setStored();
6588
}
6689

6790
private Lazy<T> ensureLazy()
6891
{
69-
if(this.wrappedLazy == null)
92+
if(this.wrappedLazy == null || !this.wrappedLazy.isLoaded())
7093
{
71-
Objects.requireNonNull(this.loader);
72-
Objects.requireNonNull(this.objectId);
73-
this.wrappedLazy = Lazy.Reference((T)this.loader.getObject(this.objectId));
94+
this.wrappedLazy = this.createNewDefaultLazyWithClearableReference();
7495
}
7596
return this.wrappedLazy;
7697
}
7798

99+
private Lazy<T> createNewDefaultLazyWithClearableReference()
100+
{
101+
Objects.requireNonNull(this.loader);
102+
Objects.requireNonNull(this.objectId);
103+
return Lazy.New(
104+
(T)this.loader.getObject(this.objectId),
105+
Swizzling.nullId(),
106+
this.loader
107+
);
108+
}
109+
78110
@SuppressWarnings("all")
79111
public static final Class<SpringDataEclipseStoreLazy.Default<?>> genericType()
80112
{
@@ -165,6 +197,8 @@ public long objectId()
165197
void setWrappedLazy(final Lazy<T> wrappedLazy)
166198
{
167199
this.wrappedLazy = wrappedLazy;
200+
// This object is already stored in the real storage. So it can get cleared.
201+
this.setStored();
168202
}
169203

170204
public T getObjectToBeWrapped()
@@ -173,12 +207,43 @@ public T getObjectToBeWrapped()
173207
}
174208

175209
@Override
176-
public SpringDataEclipseStoreLazy<T> copy()
210+
public SpringDataEclipseStoreLazy<T> copyOnlyWithReference()
177211
{
178212
return new SpringDataEclipseStoreLazy.Default(
179-
this.objectId,
213+
this.objectId(),
180214
this.loader
181215
);
182216
}
217+
218+
// TODO is this necessary?
219+
@Override
220+
public void unlink()
221+
{
222+
try
223+
{
224+
if(this.wrappedLazy != null)
225+
{
226+
final Lazy.Default wrappedDefaultLazy = (Lazy.Default)this.wrappedLazy;
227+
wrappedDefaultLazy.$unlink();
228+
final Field objectIdField = Lazy.Default.class.getDeclaredField("objectId");
229+
try(final FieldAccessModifier fam = FieldAccessModifier.prepareForField(
230+
objectIdField,
231+
wrappedDefaultLazy))
232+
{
233+
fam.writeValueOfField(wrappedDefaultLazy, Swizzling.nullId(), true);
234+
}
235+
}
236+
}
237+
catch(final Exception e)
238+
{
239+
throw new LazyNotUnlinkableException("Could not unlink lazy " + this.wrappedLazy, e);
240+
}
241+
}
242+
243+
@Override
244+
public boolean isOriginalObject()
245+
{
246+
return this.objectToBeWrapped != null;
247+
}
183248
}
184249
}

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/lazy/SpringDataEclipseStoreLazyBinaryHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void store(
8888
)
8989
{
9090
data.storeEntityHeader(Binary.referenceBinaryLength(2), this.typeId(), objectId);
91-
if(instance.getObjectToBeWrapped() != null)
91+
if(instance.isOriginalObject())
9292
{
9393
// Store unwrapped Object
9494
final long newObjectId = handler.applyEager(instance.getObjectToBeWrapped());
@@ -139,6 +139,8 @@ private <T> void updateStateT(
139139
if(objectIdOfUnwrappedObject != 0)
140140
{
141141
instance.setWrappedLazy(Lazy.Reference((T)handler.lookupObject(objectIdOfUnwrappedObject)));
142+
// Is already stored in the main storage.
143+
instance.setStored();
142144
}
143145
}
144146

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public Optional<T> findById(@Nonnull final ID id)
151151
{
152152
for(final T entity : this.storage.getEntityList(this.domainClass))
153153
{
154-
try(final FieldAccessModifier<T> fam = FieldAccessModifier.makeFieldReadable(this.getIdField(), entity))
154+
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(this.getIdField(), entity))
155155
{
156156
if(id.equals(fam.getValueOfField(entity)))
157157
{
@@ -173,7 +173,7 @@ public boolean existsById(@Nonnull final ID id)
173173
{
174174
for(final T entity : this.storage.getEntityList(this.domainClass))
175175
{
176-
try(final FieldAccessModifier<T> fam = FieldAccessModifier.makeFieldReadable(this.getIdField(), entity))
176+
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(this.getIdField(), entity))
177177
{
178178
if(id.equals(fam.getValueOfField(entity)))
179179
{
@@ -204,7 +204,7 @@ public List<T> findAllById(@Nonnull final Iterable<ID> ids)
204204
final List<T> foundEntities = new ArrayList<>();
205205
for(final T entity : this.storage.getEntityList(this.domainClass))
206206
{
207-
try(final FieldAccessModifier<T> fam = FieldAccessModifier.makeFieldReadable(this.getIdField(), entity))
207+
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(this.getIdField(), entity))
208208
{
209209
for(final ID id : ids)
210210
{

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/id/SimpleIdSetter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private void checkIfIdFieldIsFinal()
5353
@Override
5454
public void ensureId(final T objectToSetIdIn)
5555
{
56-
try(final FieldAccessModifier<T> fam = FieldAccessModifier.makeFieldReadable(
56+
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(
5757
this.idField,
5858
objectToSetIdIn))
5959
{

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/working/RecursiveWorkingCopier.java

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.TreeMap;
2828
import java.util.TreeSet;
2929

30+
import org.eclipse.serializer.reference.Lazy;
3031
import org.eclipse.serializer.reference.ObjectSwizzling;
3132
import org.slf4j.Logger;
3233
import org.slf4j.LoggerFactory;
@@ -152,7 +153,7 @@ public <E> E getOrCreateObjectForDatastore(
152153
// The object to merge back is not a working copy, but a originalObject.
153154
// Therefore, we create a copy to persist this in the storage.
154155
final E objectForDatastore = this.genericCopy(workingCopy, true);
155-
// "Why merging values of an identical object?" you might ask.
156+
// "Why merge values of an identical object?" you might ask.
156157
// Well, because some sub-objects might already be in the datastore.
157158
this.mergeValues(workingCopy, objectForDatastore, alreadyMergedTargets, changedCollector);
158159
changedCollector.collectChangedObject(objectForDatastore);
@@ -217,7 +218,7 @@ private <E> void mergeValueOfField(
217218
return;
218219
}
219220

220-
try(final FieldAccessModifier<E> fam = FieldAccessModifier.makeFieldReadable(
221+
try(final FieldAccessModifier<E> fam = FieldAccessModifier.prepareForField(
221222
field,
222223
sourceObject))
223224
{
@@ -262,6 +263,7 @@ else if(DataTypeUtil.isSpringDataEclipseStoreLazy(valueOfSourceObject))
262263
final SpringDataEclipseStoreLazy<?> newLazy =
263264
this.createNewLazy(
264265
(SpringDataEclipseStoreLazy<?>)valueOfSourceObject,
266+
(SpringDataEclipseStoreLazy<?>)valueOfTargetObject,
265267
alreadyMergedTargets,
266268
changedCollector);
267269
fam.writeValueOfField(targetObject, newLazy, true);
@@ -313,25 +315,47 @@ else if(DataTypeUtil.isSpringDataEclipseStoreLazy(valueOfSourceObject))
313315

314316
private <E> SpringDataEclipseStoreLazy<E> createNewLazy(
315317
final SpringDataEclipseStoreLazy<E> oldLazy,
318+
final SpringDataEclipseStoreLazy<?> newLazy,
316319
final MergedTargetsCollector alreadyMergedTargets,
317320
final ChangedObjectCollector changedCollector
318321
)
319322
{
320-
if(oldLazy.isLoaded())
321-
{
322-
final E copyOfWrappedObject = this.getOrCreateObjectForDatastore(
323-
oldLazy.get(),
324-
true,
325-
alreadyMergedTargets,
326-
changedCollector);
327-
return SpringDataEclipseStoreLazy.build(copyOfWrappedObject);
328-
}
329-
else
323+
if(oldLazy.isLoaded())
324+
{
325+
if(oldLazy.isOriginalObject())
330326
{
331-
// This lazy should never be used again!
332-
// It is though of as a temporary copy to merge back into the original-storage-data.
333-
return oldLazy.copy();
327+
// This object is new and in this case it is merged into the storage.
328+
if(!newLazy.isStored())
329+
{
330+
// The EclipseSerializerRegisteringCopier already creates the perfect lazy object.
331+
// No change necessary.
332+
return (SpringDataEclipseStoreLazy<E>)newLazy;
333+
}
334+
else
335+
{
336+
final Lazy<E> newLazyForBuilding = Lazy.Reference(oldLazy.get());
337+
oldLazy.unlink();
338+
newLazy.unlink();
339+
// This object is already stored but the new version must get overwritten.
340+
return SpringDataEclipseStoreLazy.Internals.build(newLazyForBuilding);
341+
}
334342
}
343+
final E copyOfWrappedObject = this.getOrCreateObjectForDatastore(
344+
oldLazy.get(),
345+
true,
346+
alreadyMergedTargets,
347+
changedCollector);
348+
oldLazy.unlink();
349+
newLazy.unlink();
350+
return SpringDataEclipseStoreLazy.build(copyOfWrappedObject);
351+
}
352+
else
353+
{
354+
oldLazy.unlink();
355+
// This lazy should never be used again!
356+
// It is though of as a temporary copy to merge back into the original-storage-data.
357+
return (SpringDataEclipseStoreLazy<E>)newLazy.copyOnlyWithReference();
358+
}
335359
}
336360

337361
/**

0 commit comments

Comments
 (0)