From cc1dd97ebcc32e1bd754073ad23def182f460452 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Fri, 3 Jun 2022 10:39:45 +0200 Subject: [PATCH] Clean up and refactor ACR and component register. --- .../src/org/argeo/cms/jcr/acr/JcrContent.java | 38 +-- .../argeo/cms/jcr/acr/JcrContentProvider.java | 8 +- .../org/argeo/api/acr/CrAttributeType.java | 32 +++ .../src/org/argeo/api/acr/CrName.java | 4 + .../argeo/api/acr/spi/AbstractContent.java | 7 +- .../argeo/api/acr/spi/ContentProvider.java | 4 +- .../argeo/api/acr/spi/ProvidedContent.java | 1 + .../argeo/api/acr/spi/ProvidedRepository.java | 2 +- .../argeo/api/acr/spi/ProvidedSession.java | 4 + .../argeo/api/uuid/MacAddressUuidFactory.java | 45 ++- org.argeo.cms/node/dc=example,dc=com.ldif | 41 +++ org.argeo.cms/node/ou=deploy,ou=node.ldif | 15 + org.argeo.cms/node/ou=roles,ou=node.ldif | 9 + org.argeo.cms/node/ou=tokens,ou=node.ldif | 4 + .../argeo/cms/acr/CmsContentRepository.java | 259 ++++-------------- .../org/argeo/cms/acr/CmsContentSession.java | 153 +++++++++++ .../src/org/argeo/cms}/acr/ContentUtils.java | 35 +-- .../src/org/argeo/cms/acr/MountManager.java | 59 ++++ ...entTypesManager.java => TypesManager.java} | 14 +- .../src/org/argeo/cms/acr/fs/FsContent.java | 27 +- .../argeo/cms/acr/fs/FsContentProvider.java | 3 +- .../src/org/argeo/cms/acr/xml/DomContent.java | 10 +- .../argeo/cms/acr/xml/DomContentProvider.java | 3 +- .../runtime/DeployedContentRepository.java | 9 +- .../src/org/argeo/cms/runtime/StaticCms.java | 24 +- .../src/org/argeo/osgi/util/OsgiRegister.java | 32 +-- .../org/argeo/util/register/Component.java | 48 +++- .../util/register/ComponentRegister.java | 16 +- .../org/argeo/util/register/RankingKey.java | 105 +++++++ .../src/org/argeo/util/register/Register.java | 10 - ...taticRegister.java => SimpleRegister.java} | 38 +-- .../org/argeo/util/register/Singleton.java | 41 --- 32 files changed, 687 insertions(+), 413 deletions(-) create mode 100644 org.argeo.cms/node/dc=example,dc=com.ldif create mode 100644 org.argeo.cms/node/ou=deploy,ou=node.ldif create mode 100644 org.argeo.cms/node/ou=roles,ou=node.ldif create mode 100644 org.argeo.cms/node/ou=tokens,ou=node.ldif create mode 100644 org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java rename {org.argeo.api.acr/src/org/argeo/api => org.argeo.cms/src/org/argeo/cms}/acr/ContentUtils.java (59%) create mode 100644 org.argeo.cms/src/org/argeo/cms/acr/MountManager.java rename org.argeo.cms/src/org/argeo/cms/acr/{ContentTypesManager.java => TypesManager.java} (95%) create mode 100644 org.argeo.util/src/org/argeo/util/register/RankingKey.java delete mode 100644 org.argeo.util/src/org/argeo/util/register/Register.java rename org.argeo.util/src/org/argeo/util/register/{StaticRegister.java => SimpleRegister.java} (78%) delete mode 100644 org.argeo.util/src/org/argeo/util/register/Singleton.java diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java index dab419794..5dd37f15b 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java @@ -27,11 +27,12 @@ import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import org.argeo.api.acr.Content; -import org.argeo.api.acr.ContentUtils; import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.spi.AbstractContent; +import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.acr.ContentUtils; import org.argeo.jcr.Jcr; import org.argeo.jcr.JcrException; import org.argeo.jcr.JcrUtils; @@ -260,31 +261,16 @@ public class JcrContent extends AbstractContent { return super.open(clss); } -// class JcrKeyIterator implements Iterator { -// private final PropertyIterator propertyIterator; -// -// protected JcrKeyIterator(PropertyIterator propertyIterator) { -// this.propertyIterator = propertyIterator; -// } -// -// @Override -// public boolean hasNext() { -// return propertyIterator.hasNext(); -// } -// -// @Override -// public QName next() { -// Property property = null; -// try { -// property = propertyIterator.nextProperty(); -// // TODO map standard property names -// return NamespaceUtils.parsePrefixedName(provider, property.getName()); -// } catch (RepositoryException e) { -// throw new JcrException("Cannot retrieve property " + property, null); -// } -// } -// -// } + @Override + public ProvidedSession getSession() { + return session; + } + + @Override + public ContentProvider getProvider() { + return provider; + } + /* * STATIC UTLITIES */ diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java index 9e0a0089a..198a2a3ed 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java @@ -12,11 +12,11 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.xml.namespace.NamespaceContext; -import org.argeo.api.acr.Content; -import org.argeo.api.acr.ContentUtils; import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.cms.acr.CmsContentRepository; +import org.argeo.cms.acr.ContentUtils; import org.argeo.cms.jcr.CmsJcrUtils; import org.argeo.jcr.JcrException; import org.argeo.jcr.JcrUtils; @@ -32,6 +32,8 @@ public class JcrContentProvider implements ContentProvider, NamespaceContext { public void start(Map properties) { mountPath = properties.get(CmsContentRepository.ACR_MOUNT_PATH_PROPERTY); + if ("/".equals(mountPath)) + throw new IllegalArgumentException("JCR content provider cannot be root /"); Objects.requireNonNull(mountPath); adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null); } @@ -45,7 +47,7 @@ public class JcrContentProvider implements ContentProvider, NamespaceContext { } @Override - public Content get(ProvidedSession contentSession, String mountPath, String relativePath) { + public ProvidedContent get(ProvidedSession contentSession, String mountPath, String relativePath) { String jcrWorkspace = ContentUtils.getParentPath(mountPath)[1]; String jcrPath = "/" + relativePath; return new JcrContent(contentSession, this, jcrWorkspace, jcrPath); diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java index ffa28af0a..446449ec0 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java @@ -4,6 +4,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.time.Instant; import java.time.format.DateTimeParseException; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; import java.util.UUID; import javax.xml.XMLConstants; @@ -58,6 +61,7 @@ public enum CrAttributeType implements ContentNameSupplier { return XMLConstants.W3C_XML_SCHEMA_NS_URI; } + /** Default parsing procedure from a String to an object. */ public static Object parse(String str) { if (str == null) throw new IllegalArgumentException("String cannot be null"); @@ -113,6 +117,34 @@ public enum CrAttributeType implements ContentNameSupplier { return STRING.getFormatter().parse(str); } + /** Utility to convert a data: URI to bytes. */ + public static byte[] bytesFromDataURI(URI uri) { + if (!"data".equals(uri.getScheme())) + throw new IllegalArgumentException("URI must have 'data' as a scheme"); + String schemeSpecificPart = uri.getSchemeSpecificPart(); + int commaIndex = schemeSpecificPart.indexOf(','); + String prefix = schemeSpecificPart.substring(0, commaIndex); + List info = Arrays.asList(prefix.split(";")); + if (!info.contains("base64")) + throw new IllegalArgumentException("URI must specify base64"); + + String base64Str = schemeSpecificPart.substring(commaIndex + 1); + return Base64.getDecoder().decode(base64Str); + + } + + /** Utility to convert bytes to a data: URI. */ + public static URI bytesToDataURI(byte[] arr) { + String base64Str = Base64.getEncoder().encodeToString(arr); + try { + final String PREFIX = "data:application/octet-stream;base64,"; + return new URI(PREFIX + base64Str); + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e); + } + + } + static class BooleanFormatter implements AttributeFormatter { /** diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java index 7e089e8b2..1b29b3661 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java @@ -34,6 +34,10 @@ public enum CrName implements ContentNameSupplier { ; public final static String CR_NAMESPACE_URI = "http://argeo.org/ns/cr"; +// public final static String CR_BASIC_NAMESPACE_URI = CR_NAMESPACE_URI + "/basic"; +// public final static String CR_OWNER_NAMESPACE_URI = CR_NAMESPACE_URI + "/owner"; +// public final static String CR_POSIX_NAMESPACE_URI = CR_NAMESPACE_URI + "/posix"; + public final static String CR_DEFAULT_PREFIX = "cr"; private final ContentName value; diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java index 6bfc7cd5f..a1a37abb3 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java @@ -15,7 +15,8 @@ import javax.xml.namespace.QName; import org.argeo.api.acr.Content; import org.argeo.api.acr.CrName; -public abstract class AbstractContent extends AbstractMap implements Content { +/** Partial reference implementation of a {@link ProvidedContent}. */ +public abstract class AbstractContent extends AbstractMap implements ProvidedContent { /* * ATTRIBUTES OPERATIONS @@ -40,6 +41,7 @@ public abstract class AbstractContent extends AbstractMap impleme return false; } + @SuppressWarnings("unchecked") @Override public Optional> getMultiple(QName key, Class clss) { Object value = get(key); @@ -153,8 +155,9 @@ public abstract class AbstractContent extends AbstractMap impleme @Override public int size() { + int count = 0; - for (QName key : keys()) { + for (Iterator it = keys().iterator(); it.hasNext();) { count++; } return count; 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 c07a43b6e..850760134 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 @@ -4,11 +4,9 @@ import java.util.Iterator; import javax.xml.namespace.NamespaceContext; -import org.argeo.api.acr.Content; - public interface ContentProvider extends NamespaceContext { - Content get(ProvidedSession session, String mountPath, String relativePath); + ProvidedContent get(ProvidedSession session, String mountPath, String relativePath); String getMountPath(); 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 d483e7fcc..d2509a49d 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 @@ -2,6 +2,7 @@ package org.argeo.api.acr.spi; import org.argeo.api.acr.Content; +/** A {@link Content} implementation. */ public interface ProvidedContent extends Content { ProvidedSession getSession(); diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java index dcf49d0d7..58c068996 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java @@ -5,11 +5,11 @@ import javax.xml.namespace.QName; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentRepository; +/** A {@link ContentRepository} implementation. */ public interface ProvidedRepository extends ContentRepository { void registerTypes(String prefix, String namespaceURI, String schemaSystemId); ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types); - boolean shouldMount(QName... types); } 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 7c6912c4e..075f7bd1b 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 @@ -6,6 +6,7 @@ import java.util.concurrent.CompletionStage; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentSession; +/** A {@link ContentSession} implementation. */ public interface ProvidedSession extends ContentSession { ProvidedRepository getRepository(); @@ -14,6 +15,9 @@ public interface ProvidedSession extends ContentSession { Content getMountPoint(String path); boolean isEditing(); + + void notifyModification(ProvidedContent content); + /* * NAMESPACE CONTEXT */ diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java index fa68df1db..ab6d55d20 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java @@ -4,6 +4,7 @@ import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; +import java.util.Enumeration; import java.util.UUID; /** @@ -16,33 +17,55 @@ public class MacAddressUuidFactory extends ConcurrentUuidFactory { public final static UuidFactory DEFAULT = new MacAddressUuidFactory(); public MacAddressUuidFactory() { - this(0); + this(0, localHardwareAddressAsNodeId()); } public MacAddressUuidFactory(long initialClockRange) { - super(initialClockRange, localHardwareAddressAsNodeId()); + this(initialClockRange, localHardwareAddressAsNodeId()); } - public static byte[] localHardwareAddressAsNodeId() { + public MacAddressUuidFactory(byte[] hardwareAddress) { + this(0, hardwareAddress); + } + + public MacAddressUuidFactory(long initialClockRange, byte[] hardwareAddress) { + super(initialClockRange, hardwareAddress); + } + + private static byte[] localHardwareAddressAsNodeId() { InetAddress localHost; try { localHost = InetAddress.getLocalHost(); NetworkInterface nic = NetworkInterface.getByInetAddress(localHost); - return hardwareAddressToNodeId(nic); + if (nic != null) + return hardwareAddressToNodeId(nic); + Enumeration netInterfaces = null; + try { + netInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + throw new IllegalStateException(e); + } + if (netInterfaces == null || !netInterfaces.hasMoreElements()) + throw new IllegalStateException("No interfaces"); + return hardwareAddressToNodeId(netInterfaces.nextElement()); } catch (UnknownHostException | SocketException e) { throw new IllegalStateException(e); } } - public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws SocketException { - byte[] hardwareAddress = nic.getHardwareAddress(); - final int length = 6; - byte[] arr = new byte[length]; - for (int i = 0; i < length; i++) { - arr[i] = hardwareAddress[length - 1 - i]; + public static byte[] hardwareAddressToNodeId(NetworkInterface nic) { + try { + byte[] hardwareAddress = nic.getHardwareAddress(); + final int length = 6; + byte[] arr = new byte[length]; + for (int i = 0; i < length; i++) { + arr[i] = hardwareAddress[length - 1 - i]; + } + return arr; + } catch (SocketException e) { + throw new IllegalStateException("Cannot retrieve hardware address from NIC", e); } - return arr; } } diff --git a/org.argeo.cms/node/dc=example,dc=com.ldif b/org.argeo.cms/node/dc=example,dc=com.ldif new file mode 100644 index 000000000..43e7ade33 --- /dev/null +++ b/org.argeo.cms/node/dc=example,dc=com.ldif @@ -0,0 +1,41 @@ +dn: dc=example,dc=com +objectClass: domain +objectClass: extensibleObject +objectClass: top +dc: example + +dn: ou=Groups,dc=example,dc=com +objectClass: organizationalUnit +objectClass: top +ou: Groups + +dn: ou=People,dc=example,dc=com +objectClass: organizationalUnit +objectClass: top +ou: People + +dn: uid=demo,ou=People,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Demo User +description: Demo user +givenName: Demo +mail: demo@localhost +sn: User +uid: demo +userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9 + +dn: uid=root,ou=People,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: person +objectClass: organizationalPerson +objectClass: top +cn: Super User +description: Superuser +givenName: Super +mail: root@localhost +sn: User +uid: root +userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9 diff --git a/org.argeo.cms/node/ou=deploy,ou=node.ldif b/org.argeo.cms/node/ou=deploy,ou=node.ldif new file mode 100644 index 000000000..52b3526c1 --- /dev/null +++ b/org.argeo.cms/node/ou=deploy,ou=node.ldif @@ -0,0 +1,15 @@ +dn: ou=org.argeo.api.userAdmin,ou=deploy,ou=node +ou: org.argeo.api.userAdmin + +dn: cn=60a26adcb1499e2b182329e94b7754275caea0b9,ou=org.argeo.api.userAdmin,ou=deploy,ou=node +baseDn: dc=example,dc=com +cn: 60a26adcb1499e2b182329e94b7754275caea0b9 + +dn: cn=f0f04f5ae5246a5678ac2be6245da45b04e623a8,ou=org.argeo.api.userAdmin,ou=deploy,ou=node +baseDn: ou=tokens,ou=node +cn: f0f04f5ae5246a5678ac2be6245da45b04e623a8 + +dn: cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node +baseDn: ou=roles,ou=node +cn: roles + diff --git a/org.argeo.cms/node/ou=roles,ou=node.ldif b/org.argeo.cms/node/ou=roles,ou=node.ldif new file mode 100644 index 000000000..85247edce --- /dev/null +++ b/org.argeo.cms/node/ou=roles,ou=node.ldif @@ -0,0 +1,9 @@ +dn: ou=node +objectClass: organizationalUnit +objectClass: top +ou: node + +dn: ou=roles,ou=node +objectClass: organizationalUnit +objectClass: top +ou: roles diff --git a/org.argeo.cms/node/ou=tokens,ou=node.ldif b/org.argeo.cms/node/ou=tokens,ou=node.ldif new file mode 100644 index 000000000..4ae9b88dd --- /dev/null +++ b/org.argeo.cms/node/ou=tokens,ou=node.ldif @@ -0,0 +1,4 @@ +dn: ou=tokens,ou=node +objectClass: organizationalUnit +objectClass: top +ou: tokens 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 d91fb16fe..092876d39 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -7,18 +7,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.Locale; import java.util.Map; -import java.util.NavigableMap; import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.xml.namespace.QName; @@ -29,17 +21,13 @@ import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.validation.Validator; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentSession; -import org.argeo.api.acr.ContentUtils; import org.argeo.api.acr.CrName; -import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.spi.ContentProvider; 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.cms.CmsAuth; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsSession; @@ -58,13 +46,8 @@ import org.xml.sax.SAXException; public class CmsContentRepository implements ProvidedRepository { private final static CmsLog log = CmsLog.getLog(CmsContentRepository.class); - private NavigableMap partitions = new TreeMap<>(); - - // TODO synchronize ? -// private NavigableMap prefixes = new TreeMap<>(); - -// private Schema schema; - private ContentTypesManager contentTypesManager; + private final MountManager mountManager; + private final TypesManager typesManager; private CmsContentSession systemSession; @@ -76,14 +59,19 @@ public class CmsContentRepository implements ProvidedRepository { public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path"; public CmsContentRepository() { - contentTypesManager = new ContentTypesManager(); - contentTypesManager.init(); - Set types = contentTypesManager.listTypes(); + // types + typesManager = new TypesManager(); + typesManager.init(); + Set types = typesManager.listTypes(); for (QName type : types) { log.debug(type); } systemSession = newSystemSession(); + + // mounts + mountManager = new MountManager(systemSession); + } protected CmsContentSession newSystemSession() { @@ -95,7 +83,7 @@ public class CmsContentRepository implements ProvidedRepository { throw new RuntimeException("Could not login as data admin", e1); } finally { } - return new CmsContentSession(loginContext.getSubject(), Locale.getDefault()); + return new CmsContentSession(this, loginContext.getSubject(), Locale.getDefault()); } public void start() { @@ -120,7 +108,7 @@ public class CmsContentRepository implements ProvidedRepository { CmsSession cmsSession = CurrentUser.getCmsSession(); CmsContentSession contentSession = userSessions.get(cmsSession); if (contentSession == null) { - final CmsContentSession newContentSession = new CmsContentSession(cmsSession.getSubject(), locale); + final CmsContentSession newContentSession = new CmsContentSession(this, cmsSession.getSubject(), locale); cmsSession.addOnCloseCallback((c) -> { newContentSession.close(); userSessions.remove(cmsSession); @@ -130,29 +118,12 @@ public class CmsContentRepository implements ProvidedRepository { return contentSession; } - public void addProvider(String base, ContentProvider provider) { - partitions.put(base, provider); - if ("/".equals(base))// root - return; - String[] parentPath = ContentUtils.getParentPath(base); - Content parent = systemSession.get(parentPath[0]); - Content mount = parent.add(parentPath[1]); - // TODO use a boolean - // ContentName name = new ContentName(CrName.MOUNT.getNamespaceURI(), - // CrName.MOUNT.name(), systemSession); - mount.put(CrName.MOUNT.get(), "true"); + public void addProvider(ContentProvider provider) { + mountManager.addStructuralContentProvider(provider); } public void registerTypes(String prefix, String namespaceURI, String schemaSystemId) { - contentTypesManager.registerTypes(prefix, namespaceURI, schemaSystemId); -// String registeredUri = prefixes.get(prefix); -// if (registeredUri == null) { -// prefixes.put(prefix, namespaceURI); -// return; -// } -// if (!registeredUri.equals(namespaceURI)) -// throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri); -// // do nothing if same namespace is already registered + typesManager.registerTypes(prefix, namespaceURI, schemaSystemId); } /* @@ -165,7 +136,7 @@ public class CmsContentRepository implements ProvidedRepository { // factory.setXIncludeAware(true); // factory.setSchema(contentTypesManager.getSchema()); // - DocumentBuilder dBuilder = contentTypesManager.newDocumentBuilder(); + DocumentBuilder dBuilder = typesManager.newDocumentBuilder(); Document document; // if (path != null && Files.exists(path)) { @@ -177,10 +148,10 @@ public class CmsContentRepository implements ProvidedRepository { document = dBuilder.newDocument(); Element root = document.createElementNS(CrName.CR_NAMESPACE_URI, CrName.ROOT.get().toPrefixedString()); - for (String prefix : contentTypesManager.getPrefixes().keySet()) { + for (String prefix : typesManager.getPrefixes().keySet()) { // root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix, // contentTypesManager.getPrefixes().get(prefix)); - DomUtils.addNamespace(root, prefix, contentTypesManager.getPrefixes().get(prefix)); + DomUtils.addNamespace(root, prefix, typesManager.getPrefixes().get(prefix)); } document.appendChild(root); @@ -193,8 +164,9 @@ public class CmsContentRepository implements ProvidedRepository { } // } - DomContentProvider contentProvider = new DomContentProvider(null, document); - addProvider("/", contentProvider); + String mountPath = "/"; + DomContentProvider contentProvider = new DomContentProvider(mountPath, document); + addProvider(contentProvider); } catch (DOMException | IOException e) { throw new IllegalStateException("Cannot init ACR root " + path, e); } @@ -208,7 +180,7 @@ public class CmsContentRepository implements ProvidedRepository { transformer.setOutputProperty(OutputKeys.INDENT, "yes"); DOMSource source = new DOMSource(document); - contentTypesManager.validate(source); + typesManager.validate(source); StreamResult result = new StreamResult(out); transformer.transform(source, result); } catch (TransformerException e) { @@ -223,176 +195,49 @@ public class CmsContentRepository implements ProvidedRepository { @Override public ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types) { String mountPath = mountPoint.getPath(); - if (partitions.containsKey(mountPath)) - // TODO check consistency with types - return partitions.get(mountPath); - DocumentBuilder dBuilder = contentTypesManager.newDocumentBuilder(); - Document document; - if (initialize) { - QName firstType = types[0]; - document = dBuilder.newDocument(); - String prefix = ((ProvidedContent) mountPoint).getSession().getPrefix(firstType.getNamespaceURI()); - Element root = document.createElementNS(firstType.getNamespaceURI(), - prefix + ":" + firstType.getLocalPart()); - DomUtils.addNamespace(root, prefix, firstType.getNamespaceURI()); - document.appendChild(root); -// try (OutputStream out = mountPoint.open(OutputStream.class)) { -// writeDom(document, out); -// } catch (IOException e) { -// throw new IllegalStateException("Cannot write mount from " + mountPoint, e); -// } - } else { - try (InputStream in = mountPoint.open(InputStream.class)) { - document = dBuilder.parse(in); - // TODO check consistency with types - } catch (IOException | SAXException e) { - throw new IllegalStateException("Cannot load mount from " + mountPoint, e); + // TODO check consistency with types + + return mountManager.getOrAddMountedProvider(mountPath, (path) -> { + DocumentBuilder dBuilder = typesManager.newDocumentBuilder(); + Document document; + if (initialize) { + QName firstType = types[0]; + document = dBuilder.newDocument(); + String prefix = ((ProvidedContent) mountPoint).getSession().getPrefix(firstType.getNamespaceURI()); + Element root = document.createElementNS(firstType.getNamespaceURI(), + prefix + ":" + firstType.getLocalPart()); + DomUtils.addNamespace(root, prefix, firstType.getNamespaceURI()); + document.appendChild(root); + } else { + try (InputStream in = mountPoint.open(InputStream.class)) { + document = dBuilder.parse(in); + // TODO check consistency with types + } catch (IOException | SAXException e) { + throw new IllegalStateException("Cannot load mount from " + mountPoint, e); + } } - } - DomContentProvider contentProvider = new DomContentProvider(mountPath, document); - partitions.put(mountPath, contentProvider); - return contentProvider; + DomContentProvider contentProvider = new DomContentProvider(path, document); + return contentProvider; + }); } @Override public boolean shouldMount(QName... types) { if (types.length == 0) - throw new IllegalArgumentException("Types must be provided"); + return false; QName firstType = types[0]; - Set registeredTypes = contentTypesManager.listTypes(); + Set registeredTypes = typesManager.listTypes(); if (registeredTypes.contains(firstType)) return true; return false; } - /* - * NAMESPACE CONTEXT - */ - - /* - * SESSION - */ - - class CmsContentSession implements ProvidedSession { - private Subject subject; - private Locale locale; - - private CompletableFuture closed = new CompletableFuture<>(); - - private CompletableFuture edition; - - public CmsContentSession(Subject subject, Locale locale) { - this.subject = subject; - this.locale = locale; - } - - public void close() { - closed.complete(this); - } - - @Override - public CompletionStage onClose() { - return closed.minimalCompletionStage(); - } - - @Override - public Content get(String path) { - Map.Entry entry = partitions.floorEntry(path); - if (entry == null) - throw new IllegalArgumentException("No entry provider found for " + path); - String mountPath = entry.getKey(); - ContentProvider provider = entry.getValue(); - String relativePath = path.substring(mountPath.length()); - if (relativePath.length() > 0 && relativePath.charAt(0) == '/') - relativePath = relativePath.substring(1); - return provider.get(CmsContentSession.this, mountPath, relativePath); - } - - @Override - public Subject getSubject() { - return subject; - } - - @Override - public Locale getLocale() { - return locale; - } - - @Override - public ProvidedRepository getRepository() { - return CmsContentRepository.this; - } - - /* - * MOUNT MANAGEMENT - */ - @Override - public Content getMountPoint(String path) { - String[] parent = ContentUtils.getParentPath(path); - ProvidedContent mountParent = (ProvidedContent) get(parent[0]); -// Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path); - return mountParent.getMountPoint(parent[1]); - } - - /* - * NAMESPACE CONTEXT - */ - - @Override - public String getNamespaceURI(String prefix) { - return NamespaceUtils.getNamespaceURI((p) -> contentTypesManager.getPrefixes().get(p), prefix); - } - - @Override - public Iterator getPrefixes(String namespaceURI) { - return NamespaceUtils.getPrefixes( - (ns) -> contentTypesManager.getPrefixes().entrySet().stream().filter(e -> e.getValue().equals(ns)) - .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), - namespaceURI); - } - - @Override - public CompletionStage edit(Consumer work) { - edition = CompletableFuture.supplyAsync(() -> { - work.accept(this); - return this; - }).thenApply((s) -> { - // TODO optimise - for (ContentProvider provider : partitions.values()) { - if (provider instanceof DomContentProvider) { - ((DomContentProvider) provider).persist(s); - } - } - return s; - }); - return edition.minimalCompletionStage(); - } - - @Override - public boolean isEditing() { - return edition != null && !edition.isDone(); - } - -// @Override -// public String findNamespace(String prefix) { -// return prefixes.get(prefix); -// } -// -// @Override -// public Set findPrefixes(String namespaceURI) { -// Set res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI)) -// .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()); -// -// return res; -// } -// -// @Override -// public String findPrefix(String namespaceURI) { -// if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX)) -// return CrName.CR_DEFAULT_PREFIX; -// return ProvidedSession.super.findPrefix(namespaceURI); -// } + MountManager getMountManager() { + return mountManager; + } + TypesManager getTypesManager() { + return typesManager; } } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java new file mode 100644 index 000000000..318080509 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java @@ -0,0 +1,153 @@ +package org.argeo.cms.acr; + +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import javax.security.auth.Subject; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.acr.xml.DomContentProvider; + +/** Implements {@link ProvidedSession}. */ +class CmsContentSession implements ProvidedSession { + final private CmsContentRepository contentRepository; + + private Subject subject; + private Locale locale; + + private CompletableFuture closed = new CompletableFuture<>(); + + private CompletableFuture edition; + + private Set modifiedProviders = new TreeSet<>(); + + public CmsContentSession(CmsContentRepository contentRepository, Subject subject, Locale locale) { + this.contentRepository = contentRepository; + this.subject = subject; + this.locale = locale; + } + + public void close() { + closed.complete(this); + } + + @Override + public CompletionStage onClose() { + return closed.minimalCompletionStage(); + } + + @Override + public Content get(String path) { + ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path); + String mountPath = contentProvider.getMountPath(); + String relativePath = path.substring(mountPath.length()); + if (relativePath.length() > 0 && relativePath.charAt(0) == '/') + relativePath = relativePath.substring(1); + ProvidedContent content = contentProvider.get(CmsContentSession.this, mountPath, relativePath); + return content; + } + + @Override + public Subject getSubject() { + return subject; + } + + @Override + public Locale getLocale() { + return locale; + } + + @Override + public ProvidedRepository getRepository() { + return contentRepository; + } + + /* + * MOUNT MANAGEMENT + */ + @Override + public Content getMountPoint(String path) { + String[] parent = ContentUtils.getParentPath(path); + ProvidedContent mountParent = (ProvidedContent) get(parent[0]); +// Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path); + return mountParent.getMountPoint(parent[1]); + } + + /* + * NAMESPACE CONTEXT + */ + + @Override + public String getNamespaceURI(String prefix) { + return NamespaceUtils.getNamespaceURI((p) -> contentRepository.getTypesManager().getPrefixes().get(p), prefix); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + return NamespaceUtils.getPrefixes((ns) -> contentRepository.getTypesManager().getPrefixes().entrySet().stream() + .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), + namespaceURI); + } + + @Override + public CompletionStage edit(Consumer work) { + edition = CompletableFuture.supplyAsync(() -> { + work.accept(this); + return this; + }).thenApply((s) -> { + // TODO optimise + for (ContentProvider provider : modifiedProviders) { + if (provider instanceof DomContentProvider) { + ((DomContentProvider) provider).persist(s); + } + } + return s; + }); + return edition.minimalCompletionStage(); + } + + @Override + public boolean isEditing() { + return edition != null && !edition.isDone(); + } + + @Override + public void notifyModification(ProvidedContent content) { + ContentProvider contentProvider = content.getProvider(); + modifiedProviders.add(contentProvider); + } + +// @Override +// public String findNamespace(String prefix) { +// return prefixes.get(prefix); +// } +// +// @Override +// public Set findPrefixes(String namespaceURI) { +// Set res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI)) +// .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()); +// +// return res; +// } +// +// @Override +// public String findPrefix(String namespaceURI) { +// if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX)) +// return CrName.CR_DEFAULT_PREFIX; +// return ProvidedSession.super.findPrefix(namespaceURI); +// } + +} \ No newline at end of file diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java similarity index 59% rename from org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java rename to org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java index 5d1f59920..d0ce5d153 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java @@ -1,15 +1,12 @@ -package org.argeo.api.acr; +package org.argeo.cms.acr; import java.io.PrintStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; import java.util.function.BiConsumer; import javax.xml.namespace.QName; +import org.argeo.api.acr.Content; + public class ContentUtils { public static void traverse(Content content, BiConsumer doIt) { traverse(content, doIt, 0); @@ -40,31 +37,7 @@ public class ContentUtils { } } - public static URI bytesToDataURI(byte[] arr) { - String base64Str = Base64.getEncoder().encodeToString(arr); - try { - final String PREFIX = "data:application/octet-stream;base64,"; - return new URI(PREFIX + base64Str); - } catch (URISyntaxException e) { - throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e); - } - - } - - public static byte[] bytesFromDataURI(URI uri) { - if (!"data".equals(uri.getScheme())) - throw new IllegalArgumentException("URI must have 'data' as a scheme"); - String schemeSpecificPart = uri.getSchemeSpecificPart(); - int commaIndex = schemeSpecificPart.indexOf(','); - String prefix = schemeSpecificPart.substring(0, commaIndex); - List info = Arrays.asList(prefix.split(";")); - if (!info.contains("base64")) - throw new IllegalArgumentException("URI must specify base64"); - - String base64Str = schemeSpecificPart.substring(commaIndex + 1); - return Base64.getDecoder().decode(base64Str); - - } + // public static boolean isString(T t) { // return t instanceof String; diff --git a/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java b/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java new file mode 100644 index 000000000..3995dc30f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java @@ -0,0 +1,59 @@ +package org.argeo.cms.acr; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.Function; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.CrName; +import org.argeo.api.acr.spi.ContentProvider; + +/** Manages the structural and dynamic mounts within the content repository. */ +class MountManager { + private final NavigableMap partitions = new TreeMap<>(); + + private final CmsContentSession systemSession; + + public MountManager(CmsContentSession systemSession) { + this.systemSession = systemSession; + } + + synchronized void addStructuralContentProvider(ContentProvider contentProvider) { + String mountPath = contentProvider.getMountPath(); + Objects.requireNonNull(mountPath); + if (partitions.containsKey(mountPath)) + throw new IllegalStateException("A provider is already registered for " + mountPath); + partitions.put(mountPath, contentProvider); + if ("/".equals(mountPath))// root + return; + String[] parentPath = ContentUtils.getParentPath(mountPath); + Content parent = systemSession.get(parentPath[0]); + Content mount = parent.add(parentPath[1]); + mount.put(CrName.MOUNT.get(), "true"); + + } + + synchronized ContentProvider getOrAddMountedProvider(String mountPath, Function factory) { + Objects.requireNonNull(factory); + if (!partitions.containsKey(mountPath)) { + ContentProvider contentProvider = factory.apply(mountPath); + if (!mountPath.equals(contentProvider.getMountPath())) + throw new IllegalArgumentException("Mount path " + mountPath + " is inconsistent with content provider " + + contentProvider.getMountPath()); + partitions.put(mountPath, contentProvider); + } + return partitions.get(mountPath); + } + + synchronized ContentProvider findContentProvider(String path) { + Map.Entry entry = partitions.floorEntry(path); + if (entry == null) + throw new IllegalArgumentException("No entry provider found for " + path); + String mountPath = entry.getKey(); + ContentProvider contentProvider = entry.getValue(); + assert mountPath.equals(contentProvider.getMountPath()); + return contentProvider; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java similarity index 95% rename from org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java rename to org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java index 48093bee0..dd2646fcd 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java @@ -32,14 +32,14 @@ import org.apache.xerces.xs.XSLoader; import org.apache.xerces.xs.XSModel; import org.apache.xerces.xs.XSNamedMap; import org.apache.xerces.xs.XSTypeDefinition; -import org.argeo.api.acr.CrName; import org.argeo.api.cms.CmsLog; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; -public class ContentTypesManager { - private final static CmsLog log = CmsLog.getLog(ContentTypesManager.class); +/** Register content types. */ +class TypesManager { + private final static CmsLog log = CmsLog.getLog(TypesManager.class); private Map prefixes = new TreeMap<>(); // immutable factories @@ -56,7 +56,7 @@ public class ContentTypesManager { private boolean validating = true; - public ContentTypesManager() { + public TypesManager() { schemaFactory = SchemaFactory.newDefaultInstance(); // types @@ -72,9 +72,9 @@ public class ContentTypesManager { public synchronized void init() { // prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI); - prefixes.put("basic", CrName.CR_NAMESPACE_URI); - prefixes.put("owner", CrName.CR_NAMESPACE_URI); - prefixes.put("posix", CrName.CR_NAMESPACE_URI); +// prefixes.put("basic", CrName.CR_NAMESPACE_URI); +// prefixes.put("owner", CrName.CR_NAMESPACE_URI); +// prefixes.put("posix", CrName.CR_NAMESPACE_URI); for (CmsContentTypes cs : CmsContentTypes.values()) { StreamSource source = new StreamSource(cs.getResource().toExternalForm()); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java index a2cc52c42..c6266ee4f 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java @@ -24,13 +24,13 @@ import javax.xml.namespace.QName; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentName; import org.argeo.api.acr.ContentResourceException; -import org.argeo.api.acr.ContentUtils; import org.argeo.api.acr.CrName; import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.spi.AbstractContent; import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.acr.ContentUtils; import org.argeo.util.FsUtils; public class FsContent extends AbstractContent implements ProvidedContent { @@ -65,7 +65,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { // TODO check file names with ':' ? if (isRoot) { String mountPath = provider.getMountPath(); - if (mountPath != null) { + if (mountPath != null && !mountPath.equals("/")) { Content mountPoint = session.getMountPoint(mountPath); this.name = mountPoint.getName(); } else { @@ -99,7 +99,26 @@ public class FsContent extends AbstractContent implements ProvidedContent { Object value; try { // We need to add user: when accessing via Files#getAttribute - value = Files.getAttribute(path, toFsAttributeKey(key)); + + if (POSIX_KEYS.containsKey(key)) { + value = Files.getAttribute(path, toFsAttributeKey(key)); + } else { + UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, + UserDefinedFileAttributeView.class); + String prefixedName = NamespaceUtils.toPrefixedName(provider, key); + if (!udfav.list().contains(prefixedName)) + return Optional.empty(); + ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName)); + udfav.read(prefixedName, buf); + buf.flip(); + if (buf.hasArray()) + value = buf.array(); + else { + byte[] arr = new byte[buf.remaining()]; + buf.get(arr); + value = arr; + } + } } catch (IOException e) { throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e); } @@ -239,7 +258,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { public Content getParent() { if (isRoot) { String mountPath = provider.getMountPath(); - if (mountPath == null) + if (mountPath == null || mountPath.equals("/")) return null; String[] parent = ContentUtils.getParentPath(mountPath); return session.get(parent[0]); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java index 3445733e1..e17384b94 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java @@ -17,6 +17,7 @@ import org.argeo.api.acr.ContentResourceException; import org.argeo.api.acr.CrName; import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; /** Access a file system as a {@link ContentProvider}. */ @@ -99,7 +100,7 @@ public class FsContentProvider implements ContentProvider { } @Override - public Content get(ProvidedSession session, String mountPath, String relativePath) { + public ProvidedContent get(ProvidedSession session, String mountPath, String relativePath) { return new FsContent(session, this, rootPath.resolve(relativePath)); } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java index 9a1a58c55..bfea129b1 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -1,7 +1,6 @@ package org.argeo.cms.acr.xml; import java.nio.CharBuffer; -import java.nio.file.Path; import java.util.HashSet; import java.util.Iterator; import java.util.Optional; @@ -14,12 +13,10 @@ import javax.xml.namespace.QName; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentName; -import org.argeo.api.acr.ContentUtils; -import org.argeo.api.acr.CrName; import org.argeo.api.acr.spi.AbstractContent; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; -import org.argeo.cms.acr.fs.FsContent; +import org.argeo.cms.acr.ContentUtils; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -242,7 +239,10 @@ public class DomContent extends AbstractContent implements ProvidedContent { public CompletableFuture write(Class clss) { if (String.class.isAssignableFrom(clss)) { CompletableFuture res = new CompletableFuture<>(); - res.thenAccept((s) -> element.setTextContent(s));// .thenRun(() -> provider.persist(session)); + res.thenAccept((s) -> { + session.notifyModification(this); + element.setTextContent(s); + }); return (CompletableFuture) res; } return super.write(clss); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java index 8caac1ad2..80523cb6c 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java @@ -17,6 +17,7 @@ import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentNotFoundException; import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.cms.acr.CmsContentRepository; import org.w3c.dom.Document; @@ -60,7 +61,7 @@ public class DomContentProvider implements ContentProvider, NamespaceContext { // } @Override - public Content get(ProvidedSession session, String mountPath, String relativePath) { + public ProvidedContent get(ProvidedSession session, String mountPath, String relativePath) { if ("".equals(relativePath)) return new DomContent(session, this, document.getDocumentElement()); if (relativePath.startsWith("/")) diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java index aaf02e06a..4fee40688 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java @@ -8,7 +8,6 @@ import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsState; import org.argeo.cms.acr.CmsContentRepository; import org.argeo.cms.acr.fs.FsContentProvider; -import org.argeo.util.LangUtils; public class DeployedContentRepository extends CmsContentRepository { private final static String ROOT_XML = "cr:root.xml"; @@ -21,8 +20,8 @@ public class DeployedContentRepository extends CmsContentRepository { initRootContentProvider(rootXml); Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE); - FsContentProvider srvContentProvider = new FsContentProvider(CmsConstants.SRV_WORKSPACE, srvPath, false); - addProvider("/" + CmsConstants.SRV_WORKSPACE, srvContentProvider); + FsContentProvider srvContentProvider = new FsContentProvider("/" + CmsConstants.SRV_WORKSPACE, srvPath, false); + addProvider(srvContentProvider); } @Override @@ -31,8 +30,8 @@ public class DeployedContentRepository extends CmsContentRepository { } public void addContentProvider(ContentProvider provider, Map properties) { - String base = LangUtils.get(properties, CmsContentRepository.ACR_MOUNT_PATH_PROPERTY); - addProvider(base, provider); +// String base = LangUtils.get(properties, CmsContentRepository.ACR_MOUNT_PATH_PROPERTY); + addProvider(provider); } public void removeContentProvider(ContentProvider provider, Map properties) { diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java index 5ffff1d0e..540da48c2 100644 --- a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java +++ b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java @@ -1,6 +1,7 @@ package org.argeo.cms.runtime; import java.util.Dictionary; +import java.util.concurrent.CompletableFuture; import org.argeo.api.cms.CmsContext; import org.argeo.api.cms.CmsDeployment; @@ -14,8 +15,7 @@ import org.argeo.osgi.transaction.SimpleTransactionManager; import org.argeo.osgi.transaction.WorkControl; import org.argeo.osgi.transaction.WorkTransaction; import org.argeo.util.register.Component; -import org.argeo.util.register.ComponentRegister; -import org.argeo.util.register.StaticRegister; +import org.argeo.util.register.SimpleRegister; import org.osgi.service.useradmin.UserAdmin; /** @@ -23,10 +23,11 @@ import org.osgi.service.useradmin.UserAdmin; * deployment. Useful for testing or AOT compilation. */ public class StaticCms { + private static SimpleRegister register = new SimpleRegister(); - public void start() { - ComponentRegister register = StaticRegister.getInstance(); + private CompletableFuture stopped = new CompletableFuture(); + public void start() { // CMS State CmsStateImpl cmsState = new CmsStateImpl(); Component cmsStateC = new Component.Builder<>(cmsState) // @@ -83,21 +84,28 @@ public class StaticCms { .addDependency(cmsDeploymentC.getType(CmsDeployment.class), cmsContext::setCmsDeployment, null) // .addDependency(userAdminC.getType(UserAdmin.class), cmsContext::setUserAdmin, null) // .build(register); - assert cmsContextC.getInstance() == cmsContext; + assert cmsContextC.get() == cmsContext; register.activate(); } public void stop() { - if (StaticRegister.getInstance().isActive()) - StaticRegister.getInstance().deactivate(); + if (register.isActive()) { + register.deactivate(); + } + register.clear(); + stopped.complete(null); + } + + public void waitForStop() { + stopped.join(); } public static void main(String[] args) { StaticCms staticCms = new StaticCms(); Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown")); staticCms.start(); - + staticCms.waitForStop(); } } diff --git a/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java b/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java index 7132b7c3f..5728b90db 100644 --- a/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java +++ b/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java @@ -5,16 +5,13 @@ import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinPool; -import org.argeo.util.register.Register; -import org.argeo.util.register.Singleton; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; -public class OsgiRegister implements Register { +public class OsgiRegister { private final BundleContext bundleContext; private Executor executor; @@ -26,8 +23,7 @@ public class OsgiRegister implements Register { this.executor = ForkJoinPool.commonPool(); } - @Override - public Singleton set(T obj, Class clss, Map attributes, Class... classes) { + public void set(T obj, Class clss, Map attributes, Class... classes) { CompletableFuture> srf = new CompletableFuture>(); CompletableFuture postRegistration = CompletableFuture.supplyAsync(() -> { List lst = new ArrayList<>(); @@ -40,18 +36,18 @@ public class OsgiRegister implements Register { srf.complete(sr); return obj; }, executor); - Singleton singleton = new Singleton(clss, postRegistration); - - shutdownStarting. // - thenCompose(singleton::prepareUnregistration). // - thenRunAsync(() -> { - try { - srf.get().unregister(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - }, executor); - return singleton; +// Singleton singleton = new Singleton(clss, postRegistration); + +// shutdownStarting. // +// thenCompose(singleton::prepareUnregistration). // +// thenRunAsync(() -> { +// try { +// srf.get().unregister(); +// } catch (InterruptedException | ExecutionException e) { +// e.printStackTrace(); +// } +// }, executor); +// return singleton; } public void shutdown() { diff --git a/org.argeo.util/src/org/argeo/util/register/Component.java b/org.argeo.util/src/org/argeo/util/register/Component.java index 66ac2ada9..cb69b133d 100644 --- a/org.argeo.util/src/org/argeo/util/register/Component.java +++ b/org.argeo.util/src/org/argeo/util/register/Component.java @@ -6,12 +6,14 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.function.Consumer; +import java.util.function.Supplier; /** * A wrapper for an object, whose dependencies and life cycle can be managed. */ -public class Component { +public class Component implements Supplier, Comparable> { private final I instance; @@ -20,6 +22,7 @@ public class Component { private final Map, PublishedType> types; private final Set> dependencies; + private final Map properties; private CompletableFuture activationStarted = null; private CompletableFuture activated = null; @@ -27,10 +30,13 @@ public class Component { private CompletableFuture deactivationStarted = null; private CompletableFuture deactivated = null; + // internal private Set> dependants = new HashSet<>(); - Component(Consumer> register, I instance, Runnable init, Runnable close, - Set> dependencies, Set> classes) { + private RankingKey rankingKey; + + Component(ComponentRegister register, I instance, Runnable init, Runnable close, Set> dependencies, + Set> classes, Map properties) { assert instance != null; assert init != null; assert close != null; @@ -64,7 +70,11 @@ public class Component { // TODO check whether context is active, so that we start right away prepareNextActivation(); - register.accept(this); + long serviceId = register.register(this); + Map props = new HashMap<>(properties); + props.put(RankingKey.SERVICE_ID, serviceId); + this.properties = Collections.unmodifiableMap(props); + rankingKey = new RankingKey(properties); } private void prepareNextActivation() { @@ -130,7 +140,8 @@ public class Component { dependants.add(dependant); } - public I getInstance() { + @Override + public I get() { return instance; } @@ -145,6 +156,15 @@ public class Component { return types.containsKey(clss); } + public Map getProperties() { + return properties; + } + + @Override + public int compareTo(Component o) { + return rankingKey.compareTo(rankingKey); + } + /** A type which has been explicitly exposed by a component. */ public static class PublishedType { private Component component; @@ -165,10 +185,14 @@ public class Component { public Class getType() { return clss; } + + public CompletionStage getValue() { + return value.minimalCompletionStage(); + } } /** Builds a {@link Component}. */ - public static class Builder { + public static class Builder implements Supplier { private final I instance; private Runnable init; @@ -176,12 +200,13 @@ public class Component { private Set> dependencies = new HashSet<>(); private Set> types = new HashSet<>(); + private final Map properties = new HashMap<>(); public Builder(I instance) { this.instance = instance; } - public Component build(Consumer> register) { + public Component build(ComponentRegister register) { // default values if (types.isEmpty()) { types.add(getInstanceClass()); @@ -195,7 +220,7 @@ public class Component { }; // instantiation - Component component = new Component(register, instance, init, close, dependencies, types); + Component component = new Component(register, instance, init, close, dependencies, types, properties); for (Dependency dependency : dependencies) { dependency.type.getPublisher().addDependant(dependency); } @@ -226,6 +251,13 @@ public class Component { return this; } + public void addProperty(String key, Object value) { + if (properties.containsKey(key)) + throw new IllegalStateException("Key " + key + " is already set."); + properties.put(key, value); + } + + @Override public I get() { return instance; } diff --git a/org.argeo.util/src/org/argeo/util/register/ComponentRegister.java b/org.argeo.util/src/org/argeo/util/register/ComponentRegister.java index eb8acba02..d5f9ea421 100644 --- a/org.argeo.util/src/org/argeo/util/register/ComponentRegister.java +++ b/org.argeo.util/src/org/argeo/util/register/ComponentRegister.java @@ -1,14 +1,24 @@ package org.argeo.util.register; import java.util.Map; -import java.util.function.Consumer; +import java.util.SortedSet; import java.util.function.Predicate; -public interface ComponentRegister extends Consumer> { - Component find(Class clss, Predicate> filter); +/** A register of components which can coordinate their activation. */ +public interface ComponentRegister { + long register(Component component); + + SortedSet> find(Class clss, Predicate> filter); Component get(Object instance); +// default PublishedType getType(Class clss) { +// SortedSet> components = find(clss, null); +// if (components.size() == 0) +// return null; +// return components.first().getType(clss); +// } + void activate(); void deactivate(); diff --git a/org.argeo.util/src/org/argeo/util/register/RankingKey.java b/org.argeo.util/src/org/argeo/util/register/RankingKey.java new file mode 100644 index 000000000..7a43e359e --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/register/RankingKey.java @@ -0,0 +1,105 @@ +package org.argeo.util.register; + +import java.util.Map; +import java.util.Objects; + +/** + * Key used to classify and filter available components. + */ +public class RankingKey implements Comparable { + public final static String SERVICE_PID = "service.pid"; + public final static String SERVICE_ID = "service.id"; + public final static String SERVICE_RANKING = "service.ranking"; + + private String pid; + private Integer ranking = 0; + private Long id = 0l; + + public RankingKey(String pid, Integer ranking, Long id) { + super(); + this.pid = pid; + this.ranking = ranking; + this.id = id; + } + + public RankingKey(Map properties) { + this.pid = properties.containsKey(SERVICE_PID) ? properties.get(SERVICE_PID).toString() : null; + this.ranking = properties.containsKey(SERVICE_RANKING) + ? Integer.parseInt(properties.get(SERVICE_RANKING).toString()) + : 0; + this.id = properties.containsKey(SERVICE_ID) ? (Long) properties.get(SERVICE_ID) : null; + } + + @Override + public int hashCode() { + Integer result = 0; + if (pid != null) + result = +pid.hashCode(); + if (ranking != null) + result = +ranking; + return result; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return new RankingKey(pid, ranking, id); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(""); + if (pid != null) + sb.append(pid); + if (ranking != null && ranking != 0) + sb.append(' ').append(ranking); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RankingKey)) + return false; + RankingKey other = (RankingKey) obj; + return Objects.equals(pid, other.pid) && Objects.equals(ranking, other.ranking) && Objects.equals(id, other.id); + } + + @Override + public int compareTo(RankingKey o) { + if (pid != null && o.pid != null) { + if (pid.equals(o.pid)) { + if (ranking.equals(o.ranking)) + if (id != null && o.id != null) + return id.compareTo(o.id); + else + return 0; + else + return ranking.compareTo(o.ranking); + } else { + return pid.compareTo(o.pid); + } + + } else { + } + return -1; + } + + public String getPid() { + return pid; + } + + public Integer getRanking() { + return ranking; + } + + public Long getId() { + return id; + } + + public static RankingKey minPid(String pid) { + return new RankingKey(pid, Integer.MIN_VALUE, null); + } + + public static RankingKey maxPid(String pid) { + return new RankingKey(pid, Integer.MAX_VALUE, null); + } +} diff --git a/org.argeo.util/src/org/argeo/util/register/Register.java b/org.argeo.util/src/org/argeo/util/register/Register.java deleted file mode 100644 index 17062809e..000000000 --- a/org.argeo.util/src/org/argeo/util/register/Register.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.util.register; - -import java.util.Map; - -/** A dynamic register of objects. */ -public interface Register { - Singleton set(T obj, Class clss, Map attributes, Class... classes); - - void shutdown(); -} diff --git a/org.argeo.util/src/org/argeo/util/register/StaticRegister.java b/org.argeo.util/src/org/argeo/util/register/SimpleRegister.java similarity index 78% rename from org.argeo.util/src/org/argeo/util/register/StaticRegister.java rename to org.argeo.util/src/org/argeo/util/register/SimpleRegister.java index 0dcba13f8..7aa9ebef9 100644 --- a/org.argeo.util/src/org/argeo/util/register/StaticRegister.java +++ b/org.argeo.util/src/org/argeo/util/register/SimpleRegister.java @@ -4,52 +4,55 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; /** A minimal component register. */ -public class StaticRegister implements ComponentRegister { - private final static StaticRegister instance = new StaticRegister(); - - public static ComponentRegister getInstance() { - return instance; - } - +public class SimpleRegister implements ComponentRegister { private final AtomicBoolean started = new AtomicBoolean(false); private final IdentityHashMap> components = new IdentityHashMap<>(); + private final AtomicLong nextServiceId = new AtomicLong(0l); @Override - public void accept(Component component) { - registerComponent(component); + public long register(Component component) { + return registerComponent(component); } @SuppressWarnings({ "unchecked" }) @Override - public synchronized Component find(Class clss, Predicate> filter) { - Set> result = new HashSet<>(); + public synchronized SortedSet> find(Class clss, + Predicate> filter) { + SortedSet> result = new TreeSet<>(); instances: for (Object instance : components.keySet()) { if (!clss.isAssignableFrom(instance.getClass())) continue instances; Component component = (Component) components.get(instance); - // TODO filter - if (component.isPublishedType(clss)) + if (component.isPublishedType(clss)) { + if (filter != null) { + filter.test(component.getProperties()); + } result.add(component); + } } if (result.isEmpty()) return null; - return result.iterator().next(); + return result; } - synchronized void registerComponent(Component component) { + synchronized long registerComponent(Component component) { if (started.get()) // TODO make it really dynamic throw new IllegalStateException("Already activated"); - if (components.containsKey(component.getInstance())) + if (components.containsKey(component.get())) throw new IllegalArgumentException("Already registered as component"); - components.put(component.getInstance(), component); + components.put(component.get(), component); + return nextServiceId.incrementAndGet(); } @Override @@ -118,5 +121,4 @@ public class StaticRegister implements ComponentRegister { public synchronized void clear() { components.clear(); } - } diff --git a/org.argeo.util/src/org/argeo/util/register/Singleton.java b/org.argeo.util/src/org/argeo/util/register/Singleton.java deleted file mode 100644 index 5d70e9aeb..000000000 --- a/org.argeo.util/src/org/argeo/util/register/Singleton.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.util.register; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Future; -import java.util.function.Consumer; - -public class Singleton { - private final Class clss; - private final CompletableFuture registrationStage; - private final List> unregistrationHooks = new ArrayList<>(); - - public Singleton(Class clss, CompletableFuture registrationStage) { - this.clss = clss; - this.registrationStage = registrationStage; - } - - CompletionStage getRegistrationStage() { - return registrationStage.minimalCompletionStage(); - } - - public void addUnregistrationHook(Consumer todo) { - unregistrationHooks.add(todo); - } - - public Future getValue() { - return registrationStage.copy(); - } - - public CompletableFuture prepareUnregistration(Void v) { - List> lst = new ArrayList<>(); - for (Consumer hook : unregistrationHooks) { - lst.add(registrationStage.thenAcceptAsync(hook)); - } - CompletableFuture prepareUnregistrationStage = CompletableFuture - .allOf(lst.toArray(new CompletableFuture[lst.size()])); - return prepareUnregistrationStage; - } -} -- 2.30.2