From: Mathieu Baudier Date: Sat, 24 Jun 2023 05:17:40 +0000 (+0200) Subject: Introduce UUID identified and openForEdit/freeze cycle X-Git-Tag: v2.3.18~11 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=369abbec35158f11bcca3651c1c3f2f7d6652226;p=lgpl%2Fargeo-commons.git Introduce UUID identified and openForEdit/freeze cycle --- diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java index 8b59f2fa2..e354672a7 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java @@ -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. * diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java index 3cf130a7c..d4162afb0 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java @@ -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; + } } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java index 5a538b57a..6d0dfbb24 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java @@ -20,7 +20,7 @@ public interface ProvidedSession extends ContentSession { void notifyModification(ProvidedContent content); - UUID getUuid(); + UUID uuid(); // Content getSessionRunDir(); diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java index dda1dac1f..b69e54f98 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java @@ -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 index 000000000..77cab371e --- /dev/null +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidIdentified.java @@ -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 uuid 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}.
+ * + *
+	 * @Override
+	 * public boolean equals(Object o) {
+	 * 	return UuidIdentified.equals(this, o);
+	 * }
+	 * 
+ */ + 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}.
+ * + *
+	 * @Override
+	 * public int hashCode() {
+	 * 	return UuidIdentified.hashCode(this);
+	 * }
+	 * 
+ */ + static int hashCode(UuidIdentified uuidIdentified) { + assert uuidIdentified != null; + return uuidIdentified.getUuid().hashCode(); + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java index a1cd3d90e..96c15bbca 100644 --- a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java @@ -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 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 index 000000000..7278c2857 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentCmsEditable.java @@ -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); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java index 89e725043..15b893bb3 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -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(); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java index 8d475fd20..be4ffea74 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java @@ -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 diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java index d324ac475..cfd90f93c 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java @@ -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() { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java index 4f5a85ddf..3a23870bd 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java @@ -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; }