Environment Details
- EclipseStore Version: 1.2.0
- JDK version: 17
- OS: Windows
- Used frameworks: e.g. Spring boot 3.2.2
Describe the bug
I am testing out eclipsestore and got it working with List in a root object. Upon trying to change it to Lazy it updates the data when the instance is still live but upon restarting my root is empty. Due to this issue I don't know If the data is really being saved or the lazy is not initialized properly
State if the problem is easily reproducible or happens intermittently.
Include stack traces or command outputs
To Reproduce
Step by step instructions to reproduce the problem.
Step one create root
import org.eclipse.serializer.reference.Lazy;
import sr.we.shekelflowcore.entity.eclipsestore.tables.Store;
import sr.we.shekelflowcore.entity.eclipsestore.tables.SuperDao;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* Eclipse store root object
*/
public class Root {
/**
* Trying to simply mimic database tables, every field here would be a Lazy List
*/
@Table(Store.class)
private Lazy<List<Store>> stores = Lazy.Reference(new ArrayList<>());
/**
* This gets the list of data within the lazy field
* @param c
* @return
* @param <T>
*/
public <T extends SuperDao> List<T> getListByClass(Class<? extends SuperDao> c) {
Lazy lazyByClass = getLazyByClass(c);
if(lazyByClass != null){
return (List<T>) lazyByClass.get();
}
return null;
}
/**
* This gets the lazy field by the provided class
* @param c
* @return
* @param <T>
*/
public <T extends SuperDao> Lazy getLazyByClass(Class<? extends SuperDao> c) {
if (c != null) {
for (Field field : Root.class.getDeclaredFields()) {
if (field.isAnnotationPresent(Table.class)) {
Table annotation = field.getAnnotation(Table.class);
if(annotation.value().equals(c)) {
try {
Lazy reference = (Lazy) field.get(this);
List<T> o = (List<T>) Lazy.get(stores);
if(o == null){
reference = (Lazy) Lazy.Reference(new ArrayList<>());
field.set(this, reference);
return (Lazy)field.get(this);
}
return reference;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
return null;
}
}
Step two create methods to add and read data
public abstract class EclipseStoreSuperService<T extends SuperDao> {
protected final EmbeddedStorageManager storageManager;
Class<T> typeParameterClass;
public EclipseStoreSuperService(EmbeddedStorageManager storageManager, Class<T> typeParameterClass) {
this.storageManager = storageManager;
this.typeParameterClass = typeParameterClass;
}
public T get(String uuId) {
Root root = (Root) storageManager.root();
List<T> list = root.getListByClass(typeParameterClass);
Optional<T> any = list.stream().filter(f -> f.getUuId().equalsIgnoreCase(uuId)).findAny();
return any.orElse(null);
}
public Stream<T> stream() {
Root root = (Root) storageManager.root();
List<T> list = root.getListByClass(typeParameterClass);
return list.stream();
}
public T update(T dao, InterExecutable<T, T> update) {
Root root = (Root) storageManager.root();
List<T> list = root.getListByClass(typeParameterClass);
if (StringUtils.isNotBlank(dao.getUuId())) {
T daoExist = get(dao.getUuId());
if (update != null && daoExist != null) {
update.build(daoExist);// this method is provided to update the existed instance
daoExist.setUuId(dao.getUuId());// to prevent people from overriding the UUID
} else {
return null;
}
} else {
dao.setUuId(UUID.randomUUID().toString());// if new we will add to the list
list.add(dao);
}
Lazy listByClass = root.getLazyByClass(typeParameterClass);
storageManager.store(listByClass);
return get(dao.getUuId());
}
public boolean delete(String uuId) {
Root root = (Root) storageManager.root();
T dao = get(uuId);
boolean remove = root.getListByClass(typeParameterClass).remove(dao);
storageManager.store(root.getLazyByClass(typeParameterClass));
return remove;
}
}
Step three this is the Store Object
import java.time.LocalDateTime;
import static java.util.Objects.requireNonNull;
/**
* This store represents a business store and is saved in the eclipsestore database
* As of now in the testing phase, the eclipsestore database does not provide any Audit.
*
* The goal here is to create as many stores as one wants and link the loyverse created items with a store.
* This will then give back an opportunity to create daily dashboard for different store owners
*/
public class Store extends SuperDao {
/**
* This ID will be used to connect the store to the Particular business
*/
Long businessId;
/**
* The ID is used to connect the store record to a particular loyverse registered store
*/
String id;
/**
* Given name
*/
String name;
/**
* Misc
*/
String address;
/**
* Misc
*/
String city;
/**
* Misc
*/
String region;
/**
* Misc
*/
String postalCode;
/**
* Misc
*/
String countryCode;
/**
* Misc
*/
String phoneNumber;
/**
* Misc
*/
String description;
/**
* Little time audit
*/
LocalDateTime createdAt;
/**
* Little time audit
*/
LocalDateTime updatedAtt;
/**
* Little time audit, TODO: WE MIGHT NOT ADD SOFT DELETE
*/
LocalDateTime deletedAt;
public Store() {
}
public Store(Long businessId, String id, String name) {
this.businessId = businessId;
this.id = id;
this.name = name;
requireNonNull(this.name, "Store name cannot be null");
requireNonNull(this.businessId, "Store parent business cannot be null");
this.createdAt = LocalDateTime.now();
this.updatedAtt = this.createdAt;
this.deletedAt = null;
}
public Long getBusinessId() {
return businessId;
}
public void setBusinessId(Long businessId) {
this.businessId = businessId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAtt() {
return updatedAtt;
}
public void setUpdatedAtt(LocalDateTime updatedAtt) {
this.updatedAtt = updatedAtt;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
@Override
public String toString() {
return "Store{" +
"businessId=" + businessId +
", id='" + id + '\'' +
", name='" + name + '\'' +
", address='" + address + '\'' +
", city='" + city + '\'' +
", region='" + region + '\'' +
", postalCode='" + postalCode + '\'' +
", countryCode='" + countryCode + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", description='" + description + '\'' +
", createdAt=" + createdAt +
", updatedAtt=" + updatedAtt +
", deletedAt=" + deletedAt +
'}';
}
}
Step 4 create store component to handle data saving
@Component
public class StoreStorage extends EclipseStoreSuperService<Store> implements IStoreStorage {
public StoreStorage(EmbeddedStorageManager storageManager) {
super(storageManager, Store.class);
}
@Override
@Read
public Store oneStore(String uuId) {
return get(uuId);
}
@Override
@Read
public List<Store> allStores(Long businessId) {
return stream().filter(store -> store.getBusinessId() != null && store.getBusinessId().compareTo(businessId) == 0).toList();
}
@Override
@Write
public Store saveOrUpdate(Store store) {
return update(store, f -> {
f.setAddress(store.getAddress());
f.setCity(store.getCity());
f.setBusinessId(store.getBusinessId());
f.setDescription(store.getDescription());
f.setCountryCode(store.getCountryCode());
f.setId(store.getId());
f.setName(store.getName());
f.setPhoneNumber(store.getPhoneNumber());
f.setRegion(store.getRegion());
f.setPostalCode(store.getPostalCode());
return f;
});
}
@Override
@Write
public boolean deleteStore(String uuId) {
return delete(uuId);
}
}
Step five create test class
import org.eclipse.store.integrations.spring.boot.types.EclipseStoreSpringBoot;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import sr.we.shekelflowcore.entity.eclipsestore.tables.Store;
import sr.we.shekelflowservice.crud.store.impl.StoreStorage;
import java.util.List;
@SpringBootTest(classes = {EclipseStoreSpringBoot.class, StoreStorage.class, StoreTest.class})
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class StoreTest {
@Autowired
private StoreStorage storage;
@Order(1)
@Test
public void addNewStoreWithValidParametersPass() {
Store store = storage.saveOrUpdate(new Store(5L,null, "Test"));
Assertions.assertNotNull(store.getUuId());
}
@Order(3)
@Test
public void retrieveAllStorePass() {
List<Store> stores = storage.allStores(5L);
Assertions.assertNotNull(stores);
Assertions.assertFalse(stores.isEmpty());
}
}
Step 6
First only test the whole class
Step 7
Second test the methods seperately
Also this is my properties file
org.eclipse.store.storage-directory=/data/prod
org.eclipse.store.auto-start=true
org.eclipse.store.root=sr.we.shekelflowcore.entity.eclipsestore.Root
org.eclipse.store.channel-count=2
logging.level.org.eclipse.store=debug
Expected behavior
I Expect the data to be saved and to be accessible when restarting (This was the case with using List)
Screenshots
Additional context
Might be doing something wrong, hope you can clarify for me.