Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layered entities example of storing/loading/updating? #315

Open
hrstoyanov opened this issue Oct 21, 2024 Discussed in #314 · 3 comments
Open

Layered entities example of storing/loading/updating? #315

hrstoyanov opened this issue Oct 21, 2024 Discussed in #314 · 3 comments

Comments

@hrstoyanov
Copy link

Hello,
I am getting this exception when trying to persist a layered entity. Looking at the "layered-entities" example, it is unclear how entities are to be stored - please show some code

 org.eclipse.serializer.persistence.binary.exceptions.BinaryPersistenceException: Only the identity layer of an entity can be persisted.
	at org.eclipse.serializer.persistence.binary.org.eclipse.serializer.entity.BinaryHandlerEntityLoading.store(BinaryHandlerEntityLoading.java:45)
	at org.eclipse.serializer.persistence.binary.org.eclipse.serializer.entity.BinaryHandlerEntityLoading.store(BinaryHandlerEntityLoading.java:35)
	at org.eclipse.serializer.persistence.binary.types.BinaryStorer$Default.storeItem(BinaryStorer.java:487)
	at org.eclipse.serializer.persistence.binary.types.BinaryStorer$Default.processItems(BinaryStorer.java:472)
	at org.eclipse.serializer.persistence.binary.types.BinaryStorer$Default.internalStore(BinaryStorer.java:455)
	at org.eclipse.serializer.persistence.binary.types.BinaryStorer$Default.storeAll(BinaryStorer.java:503)
	at org.eclipse.serializer.persistence.types.PersistenceManager$Default.storeAll(PersistenceManager.java:317)
	at org.eclipse.store.storage.types.StorageConnection.storeAll(StorageConnection.java:411)
@hrstoyanov
Copy link
Author

hrstoyanov commented Oct 21, 2024

Here is a failing junit test (java 22 with -enable-preview --add-exports java.base/jdk.internal.misc=ALL-UNNAMED):

@Test public void test() {

        FileUtils.deleteRecursively(STORE_FOLDER);
        var dataRoot = new EclipseStoreDataRoot(Lazy.Reference(new Data()) , Lazy.Reference(new UsersStorage()));
        var storageManager = EmbeddedStorage.start(dataRoot, STORE_FOLDER);
        var userStorage = dataRoot.userStorage().get();
        userStorage.initialize(storageManager); //set the storage manager which is a transient filed

        var createdAuditEvent = new AuditEvent.Created(Instant.now(), null);

        //Prepare an electronic address
        var emailFlags = new BitSet();
        ElectronicAddress.setForSecurity(emailFlags, true);
        var events = new ConcurrentLinkedQueue<AuditEvent>();
        events.add(createdAuditEvent);
        var electronicAddress = ElectronicAddressCreator.New()
                .id("[email protected]")
                .flags(emailFlags)
                .type(ElectronicAddress.Type.EMAIL)
                .created(createdAuditEvent)
                .auditEvents(Lazy.Reference(events))
                .create();

        //Prepare a pass key
        var passkeyId = new PassKey.ID(new byte[]{1,2,3,4});
        var passKeyEvents = new ConcurrentLinkedQueue<AuditEvent>();
        passKeyEvents.add(createdAuditEvent);
        var passKey = PassKeyCreator.New()
                .id(passkeyId)
                .created(createdAuditEvent)
                .auditEvents(Lazy.Reference(passKeyEvents))
                .credentialRecord(null)
                .create();

        //Register a new user with new pass key and electronic address
        var userId = Tsid.from(100);
        var user = UserCreator.New()//User.creator()
                    .id(userId)
                    .firstName("John")
                    .lastName("Smith")
                    .created(createdAuditEvent)
                    .userAttributes(new ConcurrentHashMap<>())
                    .create();
        user.putAttribute(electronicAddress);
        user.putAttribute(passKey);
        userStorage.addNewUser(user);
        return;
}

In the User entity interface, I use (generated) Updaters (ElectronicAddress and PassKey) for putAttribute(..)like this:

public interface User extends WithHumanName, WithId<Tsid>, WithStatus, Entity {
    ...

    // Shared map for all UserAttributes
    ConcurrentHashMap<Object, Object> userAttributes();

    @SuppressWarnings("unchecked")
    private static <K, A extends UserAttribute<K>> ConcurrentHashMap<K, A> castMap(ConcurrentHashMap<Object, Object> map) {
        return (ConcurrentHashMap<K, A>) (ConcurrentHashMap<?, ?>) map;
    }

    private static void updateUserReference(UserAttribute<?> attribute, User user) {
        if (attribute instanceof ElectronicAddress electronicAddress) {
            ElectronicAddressUpdater.New(electronicAddress).user(user).update();
        } else if (attribute instanceof PassKey passKey) {
            PassKeyUpdater.New(passKey).user(user).update();
        }
        // Add more conditions here for other types of UserAttributes if needed
        else throw new AssertionError(STR."Unhandled UserAttribute type: \{attribute.getClass()}");
    }

    default <K, A extends UserAttribute<K>> ConcurrentHashMap<K, A> typeSafeUserAttributes() {
        return castMap(userAttributes());
    }

    // Generic put method
    @SuppressWarnings("unchecked")
    default <K, A extends UserAttribute<K>> A putAttribute(A userAttribute) {
        A oldAttribute = (A) userAttributes().put(userAttribute.id(), userAttribute);
        if (oldAttribute == null) {
            updateUserReference(userAttribute, this);
        }
        return oldAttribute;
    }
}

and here is a sample for UserStorage, ElectronicAddress and PassKey:

public interface UserAttribute<I> extends WithId<I>, WithStatus, Entity {
    User user();
}
public interface PassKey extends UserAttribute<PassKey.ID> {}
public interface ElectronicAddress extends UserAttribute<String>{}
public class UserStorage {
    private final Map<Tsid, User> usersById = new ConcurrentHashMap<>();
    private final Map<Object, UserAttribute<?>> userAttributesById = new ConcurrentHashMap<>();
    private transient EmbeddedStorageManager persister;
    public boolean addNewUser(User newUser) {
          if (usersById.putIfAbsent(newUser.id(), newUser) == null) {
              addUserAttributes(newUser);
              storeAll(newUser, usersById, userAttributesById);
              return true;
          }
          return false;
    }
    private void addUserAttributes(User user) {
        user.typeSafeUserAttributes().values().forEach(attr -> userAttributesById.put(attr.id(), attr));
    }
}

@hrstoyanov
Copy link
Author

hrstoyanov commented Oct 23, 2024

Spent some time debugging the issue today, and it seems that exception happens because I use Passkey.ID record as part of my PassKey entity interface definition. Why is this not allowed???

public interface WithId<I> {
    I id();
}
...
public interface UserAttribute<I> extends WithId<I>, WithStatus, Entity {
    User user();
}
...
public interface PassKey extends UserAttribute<PassKey.ID> {

    Object credentialRecord();

    AtomicLong signInCount();

    record ID(byte[] idBytes) implements Comparable<ID>, Entity {

        public ID {
            Objects.requireNonNull(idBytes, "Passkey ID bytes required");
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(idBytes);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) return true;
            if (obj == null || obj.getClass() != this.getClass()) return false;
            var that = (ID) obj;
            return Arrays.equals(this.idBytes, that.idBytes);
        }

        @Override
        public int compareTo(ID o) {
            return Arrays.compare(this.idBytes, o.idBytes);
        }

        @Override
        public String toString() {
            return formatHex(idBytes);
        }

    }

}

@hrstoyanov
Copy link
Author

hrstoyanov commented Oct 23, 2024

Ok .. very strange, but the issue seem to be that I accidentally had this:

record ID(byte[] idBytes) implements Comparable<ID>, Entity

and I only need this - and it works now!

record ID(byte[] idBytes) implements Comparable<ID>

well at least may I suggest a better worded exception ,pointing out clearly what the issue is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant