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;
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
*/
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;
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);
}
}
@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);
+++ /dev/null
-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() {
-
- }
-
-}
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;
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");
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> {
/**
;
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;
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
return false;
}
+ @SuppressWarnings("unchecked")
@Override
public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
Object value = get(key);
@Override
public int size() {
+
int count = 0;
- for (QName key : keys()) {
+ for (Iterator<QName> it = keys().iterator(); it.hasNext();) {
count++;
}
return count;
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();
import org.argeo.api.acr.Content;
+/** A {@link Content} implementation. */
public interface ProvidedContent extends Content {
ProvidedSession getSession();
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);
}
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentSession;
+/** A {@link ContentSession} implementation. */
public interface ProvidedSession extends ContentSession {
ProvidedRepository getRepository();
Content getMountPoint(String path);
boolean isEditing();
+
+ void notifyModification(ProvidedContent content);
+
/*
* NAMESPACE CONTEXT
*/
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
+import java.util.Enumeration;
import java.util.UUID;
/**
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;
}
}
--- /dev/null
+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
--- /dev/null
+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
+
--- /dev/null
+dn: ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: node
+
+dn: ou=roles,ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: roles
--- /dev/null
+dn: ou=tokens,ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: tokens
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;
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;
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;
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() {
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() {
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);
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);
}
/*
// factory.setXIncludeAware(true);
// factory.setSchema(contentTypesManager.getSchema());
//
- DocumentBuilder dBuilder = contentTypesManager.newDocumentBuilder();
+ DocumentBuilder dBuilder = typesManager.newDocumentBuilder();
Document document;
// if (path != null && Files.exists(path)) {
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);
}
// }
- 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);
}
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) {
@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;
}
}
--- /dev/null
+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
+++ /dev/null
-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;
- }
-
-}
--- /dev/null
+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() {
+
+ }
+
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+
+}
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 {
// 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 {
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);
}
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]);
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}. */
}
@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));
}
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;
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;
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);
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;
// }
@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("/"))
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";
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
}
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) {
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;
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;
/**
* 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) //
.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();
}
}
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;
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<>();
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() {
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;
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;
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;
// 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() {
dependants.add(dependant);
}
- public I getInstance() {
+ @Override
+ public I get() {
return instance;
}
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;
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;
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());
};
// 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);
}
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;
}
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();
--- /dev/null
+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);
+ }
+}
+++ /dev/null
-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();
-}
--- /dev/null
+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();
+ }
+}
+++ /dev/null
-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;
- }
-}
+++ /dev/null
-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();
- }
-
-}