Introduce UUID identified and openForEdit/freeze cycle
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 24 Jun 2023 05:17:40 +0000 (07:17 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 24 Jun 2023 05:17:40 +0000 (07:17 +0200)
org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java
org.argeo.api.uuid/src/org/argeo/api/uuid/UuidIdentified.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java
org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentCmsEditable.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java

index 8b59f2fa23120784660d1c4a6e4defd3b9f44754..e354672a781c77adc1a09a009f42a3f2264c71b1 100644 (file)
@@ -54,6 +54,21 @@ public interface ContentProvider extends NamespaceContext {
        /*
         * EDITION
         */
+       /** Switch this content (and its subtree) to editing mode. */
+       default void openForEdit(ProvidedSession session, String relativePath) {
+               throw new UnsupportedOperationException();
+       }
+
+       /** Switch this content (and its subtree) to frozen mode. */
+       default void freeze(ProvidedSession session, String relativePath) {
+               throw new UnsupportedOperationException();
+       }
+
+       /** Whether this content (and its subtree) are in editing mode. */
+       default boolean isOpenForEdit(ProvidedSession session, String relativePath) {
+               throw new UnsupportedOperationException();
+       }
+
        /**
         * Called when an edition cycle is completed. Does nothing by default.
         * 
index 3cf130a7c456d35e22c5d74e961bc1c2acc0ffd1..d4162afb0d09d9db7ac8ebccc9ea8d7ea938861d 100644 (file)
@@ -6,10 +6,13 @@ import org.argeo.api.acr.Content;
 public interface ProvidedContent extends Content {
        final static String ROOT_PATH = "/";
 
+       /** The related {@link ProvidedSession}. */
        ProvidedSession getSession();
 
+       /** The {@link ContentProvider} this {@link Content} belongs to. */
        ContentProvider getProvider();
 
+       /** Depth relative to the root of the repository. */
        int getDepth();
 
        /**
@@ -27,10 +30,15 @@ public interface ProvidedContent extends Content {
         */
        String getSessionLocalId();
 
+       /**
+        * The {@link Content} within the same {@link ContentProvider} which can be used
+        * to mount another {@link ContentProvider}.
+        */
        default ProvidedContent getMountPoint(String relativePath) {
                throw new UnsupportedOperationException("This content doe not support mount");
        }
 
+       @Override
        default ProvidedContent getContent(String path) {
                Content fileNode;
                if (path.startsWith(ROOT_PATH)) {// absolute
@@ -50,4 +58,8 @@ public interface ProvidedContent extends Content {
                return true;
        }
 
+       /** Whether the related session can open this content for edit. */
+       default boolean canEdit() {
+               return false;
+       }
 }
index 5a538b57a45ec4593aca8a9267e67570393169b2..6d0dfbb245b323db00063472aa8a8c161cd790d9 100644 (file)
@@ -20,7 +20,7 @@ public interface ProvidedSession extends ContentSession {
 
        void notifyModification(ProvidedContent content);
 
-       UUID getUuid();
+       UUID uuid();
 
 //     Content getSessionRunDir();
 
index dda1dac1f7fa118ae4247b93e21bf7963c11e689..b69e54f98a5cd7f7ea59064ba2cb9d0edfeb4d38 100644 (file)
@@ -13,7 +13,7 @@ public interface CmsSession {
        final static String SESSION_UUID = "entryUUID";
        final static String SESSION_LOCAL_ID = "uniqueIdentifier";
 
-       UUID getUuid();
+       UUID uuid();
 
        String getUserRole();
 
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidIdentified.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidIdentified.java
new file mode 100644 (file)
index 0000000..77cab37
--- /dev/null
@@ -0,0 +1,57 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/**
+ * An object identified by a {@link UUID}. Typically used to fasten indexing and
+ * comparisons of objects or records. THe method to implement is {@link #uuid()}
+ * so that any record with an <code>uuid</code> field can easily be enriched
+ * with this interface.
+ */
+public interface UuidIdentified {
+       /** The UUID identifier. */
+       UUID uuid();
+
+       /** The UUID identifier, for compatibility with beans accessors. */
+       default UUID getUuid() {
+               return uuid();
+       }
+
+       /**
+        * Helper to implement the equals method of an {@link UuidIdentified}.<br/>
+        * 
+        * <pre>
+        * &#64;Override
+        * public boolean equals(Object o) {
+        *      return UuidIdentified.equals(this, o);
+        * }
+        * </pre>
+        */
+       static boolean equals(UuidIdentified uuidIdentified, Object o) {
+               assert uuidIdentified != null;
+               if (o == null)
+                       return false;
+               if (uuidIdentified == o)
+                       return true;
+               if (o instanceof UuidIdentified u)
+                       return uuidIdentified.uuid().equals(u.uuid());
+               else
+                       return false;
+       }
+
+       /**
+        * Helper to implement the hash code method of an {@link UuidIdentified}.<br/>
+        * 
+        * <pre>
+        * &#64;Override
+        * public int hashCode() {
+        *      return UuidIdentified.hashCode(this);
+        * }
+        * </pre>
+        */
+       static int hashCode(UuidIdentified uuidIdentified) {
+               assert uuidIdentified != null;
+               return uuidIdentified.getUuid().hashCode();
+       }
+
+}
index a1cd3d90e072e99b2209c98b8959c26141c8ed24..96c15bbca95660cbf7ec2b156d2ad0ab721b1278 100644 (file)
@@ -6,9 +6,14 @@ import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.api.cms.ux.CmsEditionEvent;
 import org.argeo.api.cms.ux.CmsEditionListener;
 
+/**
+ * Base class for implementing {@link CmsEditable}, mostly managing
+ * {@link CmsEditionListener}s.
+ */
 public abstract class AbstractCmsEditable implements CmsEditable {
        private IdentityHashMap<CmsEditionListener, Object> listeners = new IdentityHashMap<>();
 
+       /** Notifies listeners of a {@link CmsEditionEvent}. */
        protected void notifyListeners(CmsEditionEvent e) {
                if (CmsEditionEvent.START_EDITING == e.getType()) {
                        for (CmsEditionListener listener : listeners.keySet())
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentCmsEditable.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentCmsEditable.java
new file mode 100644 (file)
index 0000000..7278c28
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.ux.acr;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.ux.AbstractCmsEditable;
+
+/** {@link CmsEditable} semantics for a {@link Content}. */
+public class ContentCmsEditable extends AbstractCmsEditable {
+
+       private final boolean canEdit;
+       /** The path of this content, relative to its content provider. */
+       private final String relativePath;
+       private final ProvidedSession session;
+       private final ContentProvider provider;
+
+       public ContentCmsEditable(Content content) {
+               ProvidedContent providedContent = (ProvidedContent) content;
+               canEdit = providedContent.canEdit();
+               session = providedContent.getSession();
+               provider = providedContent.getProvider();
+               relativePath = ContentUtils.relativize(provider.getMountPath(), content.getPath());
+       }
+
+       @Override
+       public Boolean canEdit() {
+               return canEdit;
+       }
+
+       @Override
+       public Boolean isEditing() {
+               return provider.isOpenForEdit(session, relativePath);
+       }
+
+       @Override
+       public void startEditing() {
+               provider.openForEdit(session, relativePath);
+       }
+
+       @Override
+       public void stopEditing() {
+               provider.freeze(session, relativePath);
+       }
+
+}
index 89e725043923694498846d06b0307fd9d454fa50..15b893bb3d3ade0aab47e084fd0ace6316511910 100644 (file)
@@ -57,7 +57,7 @@ public class CmsContentRepository extends AbstractContentRepository {
                CmsSession cmsSession = CurrentUser.getCmsSession();
                CmsContentSession contentSession = userSessions.get(cmsSession);
                if (contentSession == null) {
-                       final CmsContentSession newContentSession = new CmsContentSession(this, cmsSession.getUuid(),
+                       final CmsContentSession newContentSession = new CmsContentSession(this, cmsSession.uuid(),
                                        cmsSession.getSubject(), locale, uuidFactory);
                        cmsSession.addOnCloseCallback((c) -> {
                                newContentSession.close();
index 8d475fd204853737eae48b94629c04fcdf858ea2..be4ffea746bf1b32a1fc2cc18aa6c116b300d468 100644 (file)
@@ -26,10 +26,11 @@ import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedRepository;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.api.uuid.UuidFactory;
+import org.argeo.api.uuid.UuidIdentified;
 import org.argeo.cms.CurrentUser;
 
 /** Implements {@link ProvidedSession}. */
-class CmsContentSession implements ProvidedSession {
+class CmsContentSession implements ProvidedSession, UuidIdentified {
        final private AbstractContentRepository contentRepository;
 
        private final UUID uuid;
@@ -73,7 +74,7 @@ class CmsContentSession implements ProvidedSession {
                        throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = extractRelativePath(mountPath, path);
+               String relativePath = ContentUtils.relativize(mountPath, path);
                ProvidedContent content = contentProvider.get(CmsContentSession.this, relativePath);
                return content;
        }
@@ -84,17 +85,10 @@ class CmsContentSession implements ProvidedSession {
                        throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = extractRelativePath(mountPath, path);
+               String relativePath = ContentUtils.relativize(mountPath, path);
                return contentProvider.exists(this, relativePath);
        }
 
-       private String extractRelativePath(String mountPath, String path) {
-               String relativePath = path.substring(mountPath.length());
-               if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
-                       relativePath = relativePath.substring(1);
-               return relativePath;
-       }
-
        @Override
        public Subject getSubject() {
                return subject;
@@ -161,7 +155,7 @@ class CmsContentSession implements ProvidedSession {
        }
 
        @Override
-       public UUID getUuid() {
+       public UUID uuid() {
                return uuid;
        }
 
@@ -180,11 +174,18 @@ class CmsContentSession implements ProvidedSession {
                return sessionRunDir;
        }
 
+       /*
+        * OBJECT METHODS
+        */
+
        @Override
        public boolean equals(Object o) {
-               if (o instanceof CmsContentSession session)
-                       return uuid.equals(session.uuid);
-               return false;
+               return UuidIdentified.equals(this, o);
+       }
+
+       @Override
+       public int hashCode() {
+               return UuidIdentified.hashCode(this);
        }
 
        @Override
index d324ac475283530ae658498f0bce39f305897225..cfd90f93c1d5e77aa86e900985646095204adf4c 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.cms.acr;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.StringJoiner;
 import java.util.function.BiConsumer;
 
@@ -195,6 +196,23 @@ public class ContentUtils {
                }
        }
 
+       /**
+        * Constructs a relative path between a base path and a given path.
+        * 
+        * @throws IllegalArgumentException if the base path is not an ancestor of the
+        *                                  path
+        */
+       public static String relativize(String basePath, String path) throws IllegalArgumentException {
+               Objects.requireNonNull(basePath);
+               Objects.requireNonNull(path);
+               if (!path.startsWith(basePath))
+                       throw new IllegalArgumentException(basePath + " is not an ancestor of " + path);
+               String relativePath = path.substring(basePath.length());
+               if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
+                       relativePath = relativePath.substring(1);
+               return relativePath;
+       }
+
        /** Singleton. */
        private ContentUtils() {
 
index 4f5a85ddfc998a0bbd24b33d4b4fc567bf21e565..3a23870bd0703a37608311c694d70a80ee82f672 100644 (file)
@@ -21,11 +21,12 @@ import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
+import org.argeo.api.uuid.UuidIdentified;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.osgi.service.useradmin.Authorization;
 
 /** Default CMS session implementation. */
-public class CmsSessionImpl implements CmsSession, Serializable {
+public class CmsSessionImpl implements CmsSession, Serializable, UuidIdentified {
        private static final long serialVersionUID = 1867719354246307225L;
        private final static CmsLog log = CmsLog.getLog(CmsSessionImpl.class);
 
@@ -128,7 +129,7 @@ public class CmsSessionImpl implements CmsSession, Serializable {
        }
 
        @Override
-       public UUID getUuid() {
+       public UUID uuid() {
                return uuid;
        }
 
@@ -175,6 +176,21 @@ public class CmsSessionImpl implements CmsSession, Serializable {
                views.put(uid, view);
        }
 
+       /*
+        * OBJECT METHODS
+        */
+
+       @Override
+       public boolean equals(Object o) {
+               return UuidIdentified.equals(this, o);
+       }
+
+       @Override
+       public int hashCode() {
+               return UuidIdentified.hashCode(this);
+       }
+
+       @Override
        public String toString() {
                return "CMS Session " + userDn + " localId=" + localSessionId + ", uuid=" + uuid;
        }