Clean up and refactor ACR and component register.
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 3 Jun 2022 08:39:45 +0000 (10:39 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 3 Jun 2022 08:39:45 +0000 (10:39 +0200)
35 files changed:
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java
org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java [deleted file]
org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java
org.argeo.api.acr/src/org/argeo/api/acr/CrName.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java
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/ProvidedRepository.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java
org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java
org.argeo.cms/node/dc=example,dc=com.ldif [new file with mode: 0644]
org.argeo.cms/node/ou=deploy,ou=node.ldif [new file with mode: 0644]
org.argeo.cms/node/ou=roles,ou=node.ldif [new file with mode: 0644]
org.argeo.cms/node/ou=tokens,ou=node.ldif [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 [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java [deleted file]
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/MountManager.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java
org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java
org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java
org.argeo.util/src/org/argeo/util/register/Component.java
org.argeo.util/src/org/argeo/util/register/ComponentRegister.java
org.argeo.util/src/org/argeo/util/register/RankingKey.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/register/Register.java [deleted file]
org.argeo.util/src/org/argeo/util/register/SimpleRegister.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/register/Singleton.java [deleted file]
org.argeo.util/src/org/argeo/util/register/StaticRegister.java [deleted file]

index dab41979498957a91a3906eb0402acfe4c85b57d..5dd37f15be00eb9b922f93b2b4781917dab816f0 100644 (file)
@@ -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<QName> {
-//             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
         */
index 9e0a0089ab5c4ffb6ac4e4118526a20db459c97d..198a2a3ed4480054cc87e738d770c818489d2774 100644 (file)
@@ -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<String, String> 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/ContentUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java
deleted file mode 100644 (file)
index 5d1f599..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.argeo.api.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;
-
-public class ContentUtils {
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
-               traverse(content, doIt, 0);
-       }
-
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
-               doIt.accept(content, currentDepth);
-               int nextDepth = currentDepth + 1;
-               for (Content child : content) {
-                       traverse(child, doIt, nextDepth);
-               }
-       }
-
-       public static void print(Content content, PrintStream out, int depth, boolean printText) {
-               StringBuilder sb = new StringBuilder();
-               for (int i = 0; i < depth; i++) {
-                       sb.append("  ");
-               }
-               String prefix = sb.toString();
-               out.println(prefix + content.getName());
-               for (QName key : content.keySet()) {
-                       out.println(prefix + " " + key + "=" + content.get(key));
-               }
-               if (printText) {
-                       if (content.hasText()) {
-                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
-                       }
-               }
-       }
-
-       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<String> 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 <T> boolean isString(T t) {
-//             return t instanceof String;
-//     }
-
-       /**
-        * Split a path (with '/' separator) in an array of length 2, the first part
-        * being the parent path (which could be either absolute or relative), the
-        * second one being the last segment, (guaranteed to be with '/').
-        */
-       public static String[] getParentPath(String path) {
-               int parentIndex = path.lastIndexOf('/');
-               // TODO make it more robust
-               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "/",
-                               path.substring(parentIndex + 1) };
-       }
-
-       /** Singleton. */
-       private ContentUtils() {
-
-       }
-
-}
index ffa28af0aca580a6d358030d9f1e4c038f1aa179..446449ec0b42e63b676726770c18de46632324d9 100644 (file)
@@ -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<String> 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<Boolean> {
 
                /**
index 7e089e8b22c736d25760df0422014b237e5af6dd..1b29b366132eae40ff88c5e73a564b98357608b1 100644 (file)
@@ -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;
 
index 6bfc7cd5f06e74c183835ead822aaf292f4f241e..a1a37abb3ee482536b3c1297f3c75f72b5cbd167 100644 (file)
@@ -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<QName, Object> implements Content {
+/** Partial reference implementation of a {@link ProvidedContent}. */
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
 
        /*
         * ATTRIBUTES OPERATIONS
@@ -40,6 +41,7 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
                return false;
        }
 
+       @SuppressWarnings("unchecked")
        @Override
        public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
                Object value = get(key);
@@ -153,8 +155,9 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
 
                @Override
                public int size() {
+
                        int count = 0;
-                       for (QName key : keys()) {
+                       for (Iterator<QName> it = keys().iterator(); it.hasNext();) {
                                count++;
                        }
                        return count;
index c07a43b6e3ceb1c5d08c41ca7bb811c1a02c8379..850760134258b1f10d9c8457de8fbc02e2734a85 100644 (file)
@@ -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();
 
index d483e7fcc7f9e91047e0739258f11cf4064621df..d2509a49d8a9c68642c3023b50ad8b3b90fb41de 100644 (file)
@@ -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();
 
index dcf49d0d72e95da0bf73260559edf59bad621ea2..58c068996d71df0a0b0df50b7cd62821717b3c2a 100644 (file)
@@ -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);
 }
index 7c6912c4e7f6d4baf4ff1297af140f3a78494b4c..075f7bd1beccd47a4a9c38a258c96b9ce58eec63 100644 (file)
@@ -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
         */
index fa68df1dbe2777328a87b008e932039b23e56a5b..ab6d55d207ae9576dfd92dc32b5c90a660b17bc2 100644 (file)
@@ -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<NetworkInterface> 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 (file)
index 0000000..43e7ade
--- /dev/null
@@ -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 (file)
index 0000000..52b3526
--- /dev/null
@@ -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 (file)
index 0000000..85247ed
--- /dev/null
@@ -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 (file)
index 0000000..4ae9b88
--- /dev/null
@@ -0,0 +1,4 @@
+dn: ou=tokens,ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: tokens
index d91fb16fec9ae8936fe28e72c6e889b88d27bf88..092876d397fc2bccc37622502b0fa45ceaf37ce0 100644 (file)
@@ -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<String, ContentProvider> partitions = new TreeMap<>();
-
-       // TODO synchronize ?
-//     private NavigableMap<String, String> 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<QName> types = contentTypesManager.listTypes();
+               // types
+               typesManager = new TypesManager();
+               typesManager.init();
+               Set<QName> 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<QName> registeredTypes = contentTypesManager.listTypes();
+               Set<QName> 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<ProvidedSession> closed = new CompletableFuture<>();
-
-               private CompletableFuture<ContentSession> edition;
-
-               public CmsContentSession(Subject subject, Locale locale) {
-                       this.subject = subject;
-                       this.locale = locale;
-               }
-
-               public void close() {
-                       closed.complete(this);
-               }
-
-               @Override
-               public CompletionStage<ProvidedSession> onClose() {
-                       return closed.minimalCompletionStage();
-               }
-
-               @Override
-               public Content get(String path) {
-                       Map.Entry<String, ContentProvider> 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<String> 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<ContentSession> edit(Consumer<ContentSession> 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<String> findPrefixes(String namespaceURI) {
-//                     Set<String> 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 (file)
index 0000000..3180805
--- /dev/null
@@ -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<ProvidedSession> closed = new CompletableFuture<>();
+
+       private CompletableFuture<ContentSession> edition;
+
+       private Set<ContentProvider> 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<ProvidedSession> 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<String> 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<ContentSession> edit(Consumer<ContentSession> 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<String> findPrefixes(String namespaceURI) {
+//                     Set<String> 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.cms/src/org/argeo/cms/acr/ContentTypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java
deleted file mode 100644 (file)
index 48093be..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-package org.argeo.cms.acr;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableSet;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.xml.namespace.QName;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.Source;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-
-import org.apache.xerces.impl.xs.XSImplementationImpl;
-import org.apache.xerces.impl.xs.util.StringListImpl;
-import org.apache.xerces.xs.StringList;
-import org.apache.xerces.xs.XSAttributeDeclaration;
-import org.apache.xerces.xs.XSConstants;
-import org.apache.xerces.xs.XSElementDeclaration;
-import org.apache.xerces.xs.XSException;
-import org.apache.xerces.xs.XSImplementation;
-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);
-       private Map<String, String> prefixes = new TreeMap<>();
-
-       // immutable factories
-       private SchemaFactory schemaFactory;
-
-       /** Schema sources. */
-       private List<Source> sources = new ArrayList<>();
-
-       // cached
-       private Schema schema;
-       DocumentBuilderFactory documentBuilderFactory;
-       private XSModel xsModel;
-       private NavigableSet<QName> types;
-
-       private boolean validating = true;
-
-       public ContentTypesManager() {
-               schemaFactory = SchemaFactory.newDefaultInstance();
-
-               // types
-               types = new TreeSet<>((qn1, qn2) -> {
-                       if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
-                               return qn1.getLocalPart().compareTo(qn2.getLocalPart());
-                       } else {
-                               return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
-                       }
-               });
-
-       }
-
-       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);
-
-               for (CmsContentTypes cs : CmsContentTypes.values()) {
-                       StreamSource source = new StreamSource(cs.getResource().toExternalForm());
-                       sources.add(source);
-                       if (prefixes.containsKey(cs.getDefaultPrefix()))
-                               throw new IllegalStateException("Prefix " + cs.getDefaultPrefix() + " is already mapped with "
-                                               + prefixes.get(cs.getDefaultPrefix()));
-                       prefixes.put(cs.getDefaultPrefix(), cs.getNamespace());
-               }
-
-               reload();
-       }
-
-       public synchronized void registerTypes(String defaultPrefix, String namespace, String xsdSystemId) {
-               if (prefixes.containsKey(defaultPrefix))
-                       throw new IllegalStateException(
-                                       "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix));
-               prefixes.put(defaultPrefix, namespace);
-
-               sources.add(new StreamSource(xsdSystemId));
-               reload();
-       }
-
-       public Set<QName> listTypes() {
-// TODO cache it?
-               return types;
-       }
-
-       private synchronized void reload() {
-               try {
-                       // schema
-                       schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
-
-                       // document builder factory
-                       documentBuilderFactory = DocumentBuilderFactory.newInstance();
-                       documentBuilderFactory.setNamespaceAware(true);
-                       documentBuilderFactory.setXIncludeAware(true);
-                       documentBuilderFactory.setSchema(getSchema());
-                       documentBuilderFactory.setValidating(validating);
-
-                       // XS model
-                       // TODO use JVM implementation?
-//                     DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
-//                     XSImplementation implementation = (XSImplementation) registry.getDOMImplementation("XS-Loader");
-                       XSImplementation xsImplementation = new XSImplementationImpl();
-                       XSLoader xsLoader = xsImplementation.createXSLoader(null);
-                       List<String> systemIds = new ArrayList<>();
-                       for (Source source : sources) {
-                               systemIds.add(source.getSystemId());
-                       }
-                       StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size());
-                       xsModel = xsLoader.loadURIList(sl);
-
-                       // types
-                       XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
-                       for (int i = 0; i < map.getLength(); i++) {
-                               XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
-                               QName type = new QName(eDec.getNamespace(), eDec.getName());
-                               types.add(type);
-                       }
-               } catch (XSException | SAXException e) {
-                       throw new IllegalStateException("Cannot relaod types");
-               }
-       }
-
-       public DocumentBuilder newDocumentBuilder() {
-               try {
-                       DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();
-                       dBuilder.setErrorHandler(new ErrorHandler() {
-
-                               @Override
-                               public void warning(SAXParseException exception) throws SAXException {
-                                       log.warn(exception);
-                               }
-
-                               @Override
-                               public void fatalError(SAXParseException exception) throws SAXException {
-                                       log.error(exception);
-                               }
-
-                               @Override
-                               public void error(SAXParseException exception) throws SAXException {
-                                       log.error(exception);
-                               }
-                       });
-                       return dBuilder;
-               } catch (ParserConfigurationException e) {
-                       throw new IllegalStateException("Cannot create document builder", e);
-               }
-       }
-
-       public void printTypes() {
-               try {
-
-                       // Convert top level complex type definitions to node types
-                       log.debug("\n## TYPES");
-                       XSNamedMap map = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
-                       for (int i = 0; i < map.getLength(); i++) {
-                               XSTypeDefinition tDef = (XSTypeDefinition) map.item(i);
-                               log.debug(tDef);
-                       }
-                       // Convert local (anonymous) complex type defs found in top level
-                       // element declarations
-                       log.debug("\n## ELEMENTS");
-                       map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
-                       for (int i = 0; i < map.getLength(); i++) {
-                               XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
-                               XSTypeDefinition tDef = eDec.getTypeDefinition();
-                               log.debug(eDec + ", " + tDef);
-                       }
-                       log.debug("\n## ATTRIBUTES");
-                       map = xsModel.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
-                       for (int i = 0; i < map.getLength(); i++) {
-                               XSAttributeDeclaration eDec = (XSAttributeDeclaration) map.item(i);
-                               XSTypeDefinition tDef = eDec.getTypeDefinition();
-                               log.debug(eDec.getNamespace() + ":" + eDec.getName() + ", " + tDef);
-                       }
-               } catch (ClassCastException | XSException e) {
-                       throw new RuntimeException(e);
-               }
-
-       }
-
-       public void validate(Source source) throws IOException {
-               if (!validating)
-                       return;
-               Validator validator;
-               synchronized (this) {
-                       validator = schema.newValidator();
-               }
-               try {
-                       validator.validate(source);
-               } catch (SAXException e) {
-                       throw new IllegalArgumentException("Provided source is not valid", e);
-               }
-       }
-
-       public Map<String, String> getPrefixes() {
-               return prefixes;
-       }
-
-       public List<Source> getSources() {
-               return sources;
-       }
-
-       public Schema getSchema() {
-               return schema;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
new file mode 100644 (file)
index 0000000..d0ce5d1
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.cms.acr;
+
+import java.io.PrintStream;
+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<Content, Integer> doIt) {
+               traverse(content, doIt, 0);
+       }
+
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
+               doIt.accept(content, currentDepth);
+               int nextDepth = currentDepth + 1;
+               for (Content child : content) {
+                       traverse(child, doIt, nextDepth);
+               }
+       }
+
+       public static void print(Content content, PrintStream out, int depth, boolean printText) {
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < depth; i++) {
+                       sb.append("  ");
+               }
+               String prefix = sb.toString();
+               out.println(prefix + content.getName());
+               for (QName key : content.keySet()) {
+                       out.println(prefix + " " + key + "=" + content.get(key));
+               }
+               if (printText) {
+                       if (content.hasText()) {
+                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
+                       }
+               }
+       }
+
+       
+
+//     public static <T> boolean isString(T t) {
+//             return t instanceof String;
+//     }
+
+       /**
+        * Split a path (with '/' separator) in an array of length 2, the first part
+        * being the parent path (which could be either absolute or relative), the
+        * second one being the last segment, (guaranteed to be with '/').
+        */
+       public static String[] getParentPath(String path) {
+               int parentIndex = path.lastIndexOf('/');
+               // TODO make it more robust
+               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "/",
+                               path.substring(parentIndex + 1) };
+       }
+
+       /** Singleton. */
+       private ContentUtils() {
+
+       }
+
+}
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 (file)
index 0000000..3995dc3
--- /dev/null
@@ -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<String, ContentProvider> 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<String, ContentProvider> 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<String, ContentProvider> 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/TypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java
new file mode 100644 (file)
index 0000000..dd2646f
--- /dev/null
@@ -0,0 +1,227 @@
+package org.argeo.cms.acr;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import org.apache.xerces.impl.xs.XSImplementationImpl;
+import org.apache.xerces.impl.xs.util.StringListImpl;
+import org.apache.xerces.xs.StringList;
+import org.apache.xerces.xs.XSAttributeDeclaration;
+import org.apache.xerces.xs.XSConstants;
+import org.apache.xerces.xs.XSElementDeclaration;
+import org.apache.xerces.xs.XSException;
+import org.apache.xerces.xs.XSImplementation;
+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.cms.CmsLog;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/** Register content types. */
+class TypesManager {
+       private final static CmsLog log = CmsLog.getLog(TypesManager.class);
+       private Map<String, String> prefixes = new TreeMap<>();
+
+       // immutable factories
+       private SchemaFactory schemaFactory;
+
+       /** Schema sources. */
+       private List<Source> sources = new ArrayList<>();
+
+       // cached
+       private Schema schema;
+       DocumentBuilderFactory documentBuilderFactory;
+       private XSModel xsModel;
+       private NavigableSet<QName> types;
+
+       private boolean validating = true;
+
+       public TypesManager() {
+               schemaFactory = SchemaFactory.newDefaultInstance();
+
+               // types
+               types = new TreeSet<>((qn1, qn2) -> {
+                       if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+                               return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+                       } else {
+                               return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+                       }
+               });
+
+       }
+
+       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);
+
+               for (CmsContentTypes cs : CmsContentTypes.values()) {
+                       StreamSource source = new StreamSource(cs.getResource().toExternalForm());
+                       sources.add(source);
+                       if (prefixes.containsKey(cs.getDefaultPrefix()))
+                               throw new IllegalStateException("Prefix " + cs.getDefaultPrefix() + " is already mapped with "
+                                               + prefixes.get(cs.getDefaultPrefix()));
+                       prefixes.put(cs.getDefaultPrefix(), cs.getNamespace());
+               }
+
+               reload();
+       }
+
+       public synchronized void registerTypes(String defaultPrefix, String namespace, String xsdSystemId) {
+               if (prefixes.containsKey(defaultPrefix))
+                       throw new IllegalStateException(
+                                       "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix));
+               prefixes.put(defaultPrefix, namespace);
+
+               sources.add(new StreamSource(xsdSystemId));
+               reload();
+       }
+
+       public Set<QName> listTypes() {
+// TODO cache it?
+               return types;
+       }
+
+       private synchronized void reload() {
+               try {
+                       // schema
+                       schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
+
+                       // document builder factory
+                       documentBuilderFactory = DocumentBuilderFactory.newInstance();
+                       documentBuilderFactory.setNamespaceAware(true);
+                       documentBuilderFactory.setXIncludeAware(true);
+                       documentBuilderFactory.setSchema(getSchema());
+                       documentBuilderFactory.setValidating(validating);
+
+                       // XS model
+                       // TODO use JVM implementation?
+//                     DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
+//                     XSImplementation implementation = (XSImplementation) registry.getDOMImplementation("XS-Loader");
+                       XSImplementation xsImplementation = new XSImplementationImpl();
+                       XSLoader xsLoader = xsImplementation.createXSLoader(null);
+                       List<String> systemIds = new ArrayList<>();
+                       for (Source source : sources) {
+                               systemIds.add(source.getSystemId());
+                       }
+                       StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size());
+                       xsModel = xsLoader.loadURIList(sl);
+
+                       // types
+                       XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+                       for (int i = 0; i < map.getLength(); i++) {
+                               XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
+                               QName type = new QName(eDec.getNamespace(), eDec.getName());
+                               types.add(type);
+                       }
+               } catch (XSException | SAXException e) {
+                       throw new IllegalStateException("Cannot relaod types");
+               }
+       }
+
+       public DocumentBuilder newDocumentBuilder() {
+               try {
+                       DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();
+                       dBuilder.setErrorHandler(new ErrorHandler() {
+
+                               @Override
+                               public void warning(SAXParseException exception) throws SAXException {
+                                       log.warn(exception);
+                               }
+
+                               @Override
+                               public void fatalError(SAXParseException exception) throws SAXException {
+                                       log.error(exception);
+                               }
+
+                               @Override
+                               public void error(SAXParseException exception) throws SAXException {
+                                       log.error(exception);
+                               }
+                       });
+                       return dBuilder;
+               } catch (ParserConfigurationException e) {
+                       throw new IllegalStateException("Cannot create document builder", e);
+               }
+       }
+
+       public void printTypes() {
+               try {
+
+                       // Convert top level complex type definitions to node types
+                       log.debug("\n## TYPES");
+                       XSNamedMap map = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
+                       for (int i = 0; i < map.getLength(); i++) {
+                               XSTypeDefinition tDef = (XSTypeDefinition) map.item(i);
+                               log.debug(tDef);
+                       }
+                       // Convert local (anonymous) complex type defs found in top level
+                       // element declarations
+                       log.debug("\n## ELEMENTS");
+                       map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+                       for (int i = 0; i < map.getLength(); i++) {
+                               XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
+                               XSTypeDefinition tDef = eDec.getTypeDefinition();
+                               log.debug(eDec + ", " + tDef);
+                       }
+                       log.debug("\n## ATTRIBUTES");
+                       map = xsModel.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
+                       for (int i = 0; i < map.getLength(); i++) {
+                               XSAttributeDeclaration eDec = (XSAttributeDeclaration) map.item(i);
+                               XSTypeDefinition tDef = eDec.getTypeDefinition();
+                               log.debug(eDec.getNamespace() + ":" + eDec.getName() + ", " + tDef);
+                       }
+               } catch (ClassCastException | XSException e) {
+                       throw new RuntimeException(e);
+               }
+
+       }
+
+       public void validate(Source source) throws IOException {
+               if (!validating)
+                       return;
+               Validator validator;
+               synchronized (this) {
+                       validator = schema.newValidator();
+               }
+               try {
+                       validator.validate(source);
+               } catch (SAXException e) {
+                       throw new IllegalArgumentException("Provided source is not valid", e);
+               }
+       }
+
+       public Map<String, String> getPrefixes() {
+               return prefixes;
+       }
+
+       public List<Source> getSources() {
+               return sources;
+       }
+
+       public Schema getSchema() {
+               return schema;
+       }
+
+}
index a2cc52c42850f54bd2d7e25258b3cab866f10993..c6266ee4fc2d8fd489be7171f9dcac104341d3ed 100644 (file)
@@ -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]);
index 3445733e18a13ff1727c20fb139ed82c9ba49c5f..e17384b942162d7cefbde3fde1fdb0682ad3674f 100644 (file)
@@ -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));
        }
 
index 9a1a58c55a7ef3162739d8c7fed32bceed5d0e01..bfea129b11110a7561a9c3b38a1828851b2e58c1 100644 (file)
@@ -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 <A> CompletableFuture<A> write(Class<A> clss) {
                if (String.class.isAssignableFrom(clss)) {
                        CompletableFuture<String> res = new CompletableFuture<>();
-                       res.thenAccept((s) -> element.setTextContent(s));// .thenRun(() -> provider.persist(session));
+                       res.thenAccept((s) -> {
+                               session.notifyModification(this);
+                               element.setTextContent(s);
+                       });
                        return (CompletableFuture<A>) res;
                }
                return super.write(clss);
index 8caac1ad29a2629e11e09b7c72dc2857ead361f2..80523cb6c89898259252b06266dc1876918c641a 100644 (file)
@@ -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("/"))
index aaf02e06a5089911b6751d35aab00dd7c5865324..4fee406886e23d4186a8cfaafcfeb97a7ef607e6 100644 (file)
@@ -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<String, Object> 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<String, Object> properties) {
index 5ffff1d0e3a1757d2e6a36b5b28fe4aa8baedad4..540da48c2bdf74d6ccb882a45b210f70ebb9e2b6 100644 (file)
@@ -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<Void> stopped = new CompletableFuture<Void>();
 
+       public void start() {
                // CMS State
                CmsStateImpl cmsState = new CmsStateImpl();
                Component<CmsStateImpl> 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();
        }
 
 }
index 7132b7c3f05d82aa55e6d7c744c2b516e063e1d8..5728b90db2609faf8da1266b0ea62306e9e0e3a9 100644 (file)
@@ -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 <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes) {
+       public <T> void set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes) {
                CompletableFuture<ServiceRegistration<?>> srf = new CompletableFuture<ServiceRegistration<?>>();
                CompletableFuture<T> postRegistration = CompletableFuture.supplyAsync(() -> {
                        List<String> lst = new ArrayList<>();
@@ -40,18 +36,18 @@ public class OsgiRegister implements Register {
                        srf.complete(sr);
                        return obj;
                }, executor);
-               Singleton<T> singleton = new Singleton<T>(clss, postRegistration);
-
-               shutdownStarting. //
-                               thenCompose(singleton::prepareUnregistration). //
-                               thenRunAsync(() -> {
-                                       try {
-                                               srf.get().unregister();
-                                       } catch (InterruptedException | ExecutionException e) {
-                                               e.printStackTrace();
-                                       }
-                               }, executor);
-               return singleton;
+//             Singleton<T> singleton = new Singleton<T>(clss, postRegistration);
+
+//             shutdownStarting. //
+//                             thenCompose(singleton::prepareUnregistration). //
+//                             thenRunAsync(() -> {
+//                                     try {
+//                                             srf.get().unregister();
+//                                     } catch (InterruptedException | ExecutionException e) {
+//                                             e.printStackTrace();
+//                                     }
+//                             }, executor);
+//             return singleton;
        }
 
        public void shutdown() {
index 66ac2ada9233b9d9b121eb24b6d29648ef3e4869..cb69b133dd4ebb67c4e322548806add7e7590a5c 100644 (file)
@@ -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<I> {
+public class Component<I> implements Supplier<I>, Comparable<Component<?>> {
 
        private final I instance;
 
@@ -20,6 +22,7 @@ public class Component<I> {
 
        private final Map<Class<? super I>, PublishedType<? super I>> types;
        private final Set<Dependency<?>> dependencies;
+       private final Map<String, Object> properties;
 
        private CompletableFuture<Void> activationStarted = null;
        private CompletableFuture<Void> activated = null;
@@ -27,10 +30,13 @@ public class Component<I> {
        private CompletableFuture<Void> deactivationStarted = null;
        private CompletableFuture<Void> deactivated = null;
 
+       // internal
        private Set<Dependency<?>> dependants = new HashSet<>();
 
-       Component(Consumer<Component<?>> register, I instance, Runnable init, Runnable close,
-                       Set<Dependency<?>> dependencies, Set<Class<? super I>> classes) {
+       private RankingKey rankingKey;
+
+       Component(ComponentRegister register, I instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies,
+                       Set<Class<? super I>> classes, Map<String, Object> properties) {
                assert instance != null;
                assert init != null;
                assert close != null;
@@ -64,7 +70,11 @@ public class Component<I> {
                // TODO check whether context is active, so that we start right away
                prepareNextActivation();
 
-               register.accept(this);
+               long serviceId = register.register(this);
+               Map<String, Object> 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<I> {
                dependants.add(dependant);
        }
 
-       public I getInstance() {
+       @Override
+       public I get() {
                return instance;
        }
 
@@ -145,6 +156,15 @@ public class Component<I> {
                return types.containsKey(clss);
        }
 
+       public Map<String, Object> 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<T> {
                private Component<? extends T> component;
@@ -165,10 +185,14 @@ public class Component<I> {
                public Class<T> getType() {
                        return clss;
                }
+
+               public CompletionStage<T> getValue() {
+                       return value.minimalCompletionStage();
+               }
        }
 
        /** Builds a {@link Component}. */
-       public static class Builder<I> {
+       public static class Builder<I> implements Supplier<I> {
                private final I instance;
 
                private Runnable init;
@@ -176,12 +200,13 @@ public class Component<I> {
 
                private Set<Dependency<?>> dependencies = new HashSet<>();
                private Set<Class<? super I>> types = new HashSet<>();
+               private final Map<String, Object> properties = new HashMap<>();
 
                public Builder(I instance) {
                        this.instance = instance;
                }
 
-               public Component<I> build(Consumer<Component<?>> register) {
+               public Component<I> build(ComponentRegister register) {
                        // default values
                        if (types.isEmpty()) {
                                types.add(getInstanceClass());
@@ -195,7 +220,7 @@ public class Component<I> {
                                };
 
                        // instantiation
-                       Component<I> component = new Component<I>(register, instance, init, close, dependencies, types);
+                       Component<I> component = new Component<I>(register, instance, init, close, dependencies, types, properties);
                        for (Dependency<?> dependency : dependencies) {
                                dependency.type.getPublisher().addDependant(dependency);
                        }
@@ -226,6 +251,13 @@ public class Component<I> {
                        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;
                }
index eb8acba02bcaeb97314b7f8872b43c2a20e63419..d5f9ea4211d67c774cddfa25a9d3b3a7639323fe 100644 (file)
@@ -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<?>> {
-       <T> Component<? extends T> find(Class<T> clss, Predicate<Map<String, Object>> filter);
+/** A register of components which can coordinate their activation. */
+public interface ComponentRegister {
+       long register(Component<?> component);
+
+       <T> SortedSet<Component<? extends T>> find(Class<T> clss, Predicate<Map<String, Object>> filter);
 
        Component<?> get(Object instance);
 
+//     default <T> PublishedType<T> getType(Class<T> clss) {
+//             SortedSet<Component<? extends T>> 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 (file)
index 0000000..7a43e35
--- /dev/null
@@ -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<RankingKey> {
+       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<String, Object> 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 (file)
index 1706280..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.argeo.util.register;
-
-import java.util.Map;
-
-/** A dynamic register of objects. */
-public interface Register {
-       <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes);
-
-       void shutdown();
-}
diff --git a/org.argeo.util/src/org/argeo/util/register/SimpleRegister.java b/org.argeo.util/src/org/argeo/util/register/SimpleRegister.java
new file mode 100644 (file)
index 0000000..7aa9ebe
--- /dev/null
@@ -0,0 +1,124 @@
+package org.argeo.util.register;
+
+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 SimpleRegister implements ComponentRegister {
+       private final AtomicBoolean started = new AtomicBoolean(false);
+       private final IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
+       private final AtomicLong nextServiceId = new AtomicLong(0l);
+
+       @Override
+       public long register(Component<?> component) {
+               return registerComponent(component);
+       }
+
+       @SuppressWarnings({ "unchecked" })
+       @Override
+       public synchronized <T> SortedSet<Component<? extends T>> find(Class<T> clss,
+                       Predicate<Map<String, Object>> filter) {
+               SortedSet<Component<? extends T>> result = new TreeSet<>();
+               instances: for (Object instance : components.keySet()) {
+                       if (!clss.isAssignableFrom(instance.getClass()))
+                               continue instances;
+                       Component<? extends T> component = (Component<? extends T>) components.get(instance);
+
+                       if (component.isPublishedType(clss)) {
+                               if (filter != null) {
+                                       filter.test(component.getProperties());
+                               }
+                               result.add(component);
+                       }
+               }
+               if (result.isEmpty())
+                       return null;
+               return result;
+
+       }
+
+       synchronized long registerComponent(Component<?> component) {
+               if (started.get()) // TODO make it really dynamic
+                       throw new IllegalStateException("Already activated");
+               if (components.containsKey(component.get()))
+                       throw new IllegalArgumentException("Already registered as component");
+               components.put(component.get(), component);
+               return nextServiceId.incrementAndGet();
+       }
+
+       @Override
+       public synchronized Component<?> get(Object instance) {
+               if (!components.containsKey(instance))
+                       throw new IllegalArgumentException("Not registered as component");
+               return components.get(instance);
+       }
+
+       @Override
+       public synchronized void activate() {
+               if (started.get())
+                       throw new IllegalStateException("Already activated");
+               Set<CompletableFuture<?>> constraints = new HashSet<>();
+               for (Component<?> component : components.values()) {
+                       component.startActivating();
+                       constraints.add(component.getActivated());
+               }
+
+               // wait
+               try {
+                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
+                                       .get();
+               } catch (InterruptedException e) {
+                       throw new RuntimeException("Register activation has been interrupted", e);
+               } catch (ExecutionException e) {
+                       if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
+                               throw (RuntimeException) e.getCause();
+                       } else {
+                               throw new IllegalStateException("Cannot activate register", e.getCause());
+                       }
+               }
+       }
+
+       @Override
+       public synchronized void deactivate() {
+               if (!started.get())
+                       throw new IllegalStateException("Not activated");
+               Set<CompletableFuture<?>> constraints = new HashSet<>();
+               for (Component<?> component : components.values()) {
+                       component.startDeactivating();
+                       constraints.add(component.getDeactivated());
+               }
+
+               // wait
+               try {
+                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
+                                       .get();
+               } catch (InterruptedException e) {
+                       throw new RuntimeException("Register deactivation has been interrupted", e);
+               } catch (ExecutionException e) {
+                       if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
+                               throw (RuntimeException) e.getCause();
+                       } else {
+                               throw new IllegalStateException("Cannot deactivate register", e.getCause());
+                       }
+               }
+       }
+
+       @Override
+       public synchronized boolean isActive() {
+               return started.get();
+       }
+
+       @Override
+       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 (file)
index 5d70e9a..0000000
+++ /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<T> {
-       private final Class<T> clss;
-       private final CompletableFuture<T> registrationStage;
-       private final List<Consumer<T>> unregistrationHooks = new ArrayList<>();
-
-       public Singleton(Class<T> clss, CompletableFuture<T> registrationStage) {
-               this.clss = clss;
-               this.registrationStage = registrationStage;
-       }
-
-       CompletionStage<T> getRegistrationStage() {
-               return registrationStage.minimalCompletionStage();
-       }
-
-       public void addUnregistrationHook(Consumer<T> todo) {
-               unregistrationHooks.add(todo);
-       }
-
-       public Future<T> getValue() {
-               return registrationStage.copy();
-       }
-
-       public CompletableFuture<Void> prepareUnregistration(Void v) {
-               List<CompletableFuture<Void>> lst = new ArrayList<>();
-               for (Consumer<T> hook : unregistrationHooks) {
-                       lst.add(registrationStage.thenAcceptAsync(hook));
-               }
-               CompletableFuture<Void> prepareUnregistrationStage = CompletableFuture
-                               .allOf(lst.toArray(new CompletableFuture<?>[lst.size()]));
-               return prepareUnregistrationStage;
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/register/StaticRegister.java b/org.argeo.util/src/org/argeo/util/register/StaticRegister.java
deleted file mode 100644 (file)
index 0dcba13..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.argeo.util.register;
-
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicBoolean;
-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;
-       }
-
-       private final AtomicBoolean started = new AtomicBoolean(false);
-       private final IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
-
-       @Override
-       public void accept(Component<?> component) {
-               registerComponent(component);
-       }
-
-       @SuppressWarnings({ "unchecked" })
-       @Override
-       public synchronized <T> Component<? extends T> find(Class<T> clss, Predicate<Map<String, Object>> filter) {
-               Set<Component<? extends T>> result = new HashSet<>();
-               instances: for (Object instance : components.keySet()) {
-                       if (!clss.isAssignableFrom(instance.getClass()))
-                               continue instances;
-                       Component<? extends T> component = (Component<? extends T>) components.get(instance);
-
-                       // TODO filter
-                       if (component.isPublishedType(clss))
-                               result.add(component);
-               }
-               if (result.isEmpty())
-                       return null;
-               return result.iterator().next();
-
-       }
-
-       synchronized void registerComponent(Component<?> component) {
-               if (started.get()) // TODO make it really dynamic
-                       throw new IllegalStateException("Already activated");
-               if (components.containsKey(component.getInstance()))
-                       throw new IllegalArgumentException("Already registered as component");
-               components.put(component.getInstance(), component);
-       }
-
-       @Override
-       public synchronized Component<?> get(Object instance) {
-               if (!components.containsKey(instance))
-                       throw new IllegalArgumentException("Not registered as component");
-               return components.get(instance);
-       }
-
-       @Override
-       public synchronized void activate() {
-               if (started.get())
-                       throw new IllegalStateException("Already activated");
-               Set<CompletableFuture<?>> constraints = new HashSet<>();
-               for (Component<?> component : components.values()) {
-                       component.startActivating();
-                       constraints.add(component.getActivated());
-               }
-
-               // wait
-               try {
-                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
-                                       .get();
-               } catch (InterruptedException e) {
-                       throw new RuntimeException("Register activation has been interrupted", e);
-               } catch (ExecutionException e) {
-                       if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
-                               throw (RuntimeException) e.getCause();
-                       } else {
-                               throw new IllegalStateException("Cannot activate register", e.getCause());
-                       }
-               }
-       }
-
-       @Override
-       public synchronized void deactivate() {
-               if (!started.get())
-                       throw new IllegalStateException("Not activated");
-               Set<CompletableFuture<?>> constraints = new HashSet<>();
-               for (Component<?> component : components.values()) {
-                       component.startDeactivating();
-                       constraints.add(component.getDeactivated());
-               }
-
-               // wait
-               try {
-                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
-                                       .get();
-               } catch (InterruptedException e) {
-                       throw new RuntimeException("Register deactivation has been interrupted", e);
-               } catch (ExecutionException e) {
-                       if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
-                               throw (RuntimeException) e.getCause();
-                       } else {
-                               throw new IllegalStateException("Cannot deactivate register", e.getCause());
-                       }
-               }
-       }
-
-       @Override
-       public synchronized boolean isActive() {
-               return started.get();
-       }
-
-       @Override
-       public synchronized void clear() {
-               components.clear();
-       }
-
-}