X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Facr%2FCmsContentRepository.java;h=d91fb16fec9ae8936fe28e72c6e889b88d27bf88;hb=e3db2eba9a7f8380a6f76d7b0e6cd4825e91893e;hp=9cd8bd22dd6e8eef8670a45fada48cf5a8037f5f;hpb=7d2a002f5dcfe8a8c7b29803b70d4b1aff265ed1;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java index 9cd8bd22d..d91fb16fe 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -1,38 +1,104 @@ package org.argeo.cms.acr; -import java.security.AccessController; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +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; +import org.argeo.cms.acr.xml.DomContentProvider; +import org.argeo.cms.acr.xml.DomUtils; +import org.argeo.cms.auth.CurrentUser; import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; +/** + * Base implementation of a {@link ProvidedRepository} integrated with a CMS. + */ public class CmsContentRepository implements ProvidedRepository { + private final static CmsLog log = CmsLog.getLog(CmsContentRepository.class); + private NavigableMap partitions = new TreeMap<>(); // TODO synchronize ? - private NavigableMap prefixes = new TreeMap<>(); +// private NavigableMap prefixes = new TreeMap<>(); + +// private Schema schema; + private ContentTypesManager contentTypesManager; + + private CmsContentSession systemSession; + + private Map userSessions = Collections.synchronizedMap(new HashMap<>()); + + // utilities + private TransformerFactory transformerFactory = TransformerFactory.newInstance(); + + public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path"; public CmsContentRepository() { - 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); + contentTypesManager = new ContentTypesManager(); + contentTypesManager.init(); + Set types = contentTypesManager.listTypes(); + for (QName type : types) { + log.debug(type); + } + + systemSession = newSystemSession(); } - public void start() { + protected CmsContentSession newSystemSession() { + LoginContext loginContext; + try { + loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName()); + loginContext.login(); + } catch (LoginException e1) { + throw new RuntimeException("Could not login as data admin", e1); + } finally { + } + return new CmsContentSession(loginContext.getSubject(), Locale.getDefault()); + } + public void start() { } public void stop() { @@ -50,23 +116,153 @@ public class CmsContentRepository implements ProvidedRepository { @Override public ContentSession get(Locale locale) { - Subject subject = Subject.getSubject(AccessController.getContext()); - return new CmsContentSession(subject, locale); + // Subject subject = Subject.getSubject(AccessController.getContext()); + CmsSession cmsSession = CurrentUser.getCmsSession(); + CmsContentSession contentSession = userSessions.get(cmsSession); + if (contentSession == null) { + final CmsContentSession newContentSession = new CmsContentSession(cmsSession.getSubject(), locale); + cmsSession.addOnCloseCallback((c) -> { + newContentSession.close(); + userSessions.remove(cmsSession); + }); + contentSession = newContentSession; + } + 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 registerPrefix(String prefix, String namespaceURI) { - String registeredUri = prefixes.get(prefix); - if (registeredUri == null) { - prefixes.put(prefix, namespaceURI); - return; + 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 + } + + /* + * FACTORIES + */ + public void initRootContentProvider(Path path) { + try { +// DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); +// factory.setNamespaceAware(true); +// factory.setXIncludeAware(true); +// factory.setSchema(contentTypesManager.getSchema()); +// + DocumentBuilder dBuilder = contentTypesManager.newDocumentBuilder(); + + Document document; +// if (path != null && Files.exists(path)) { +// InputSource inputSource = new InputSource(path.toAbsolutePath().toUri().toString()); +// inputSource.setEncoding(StandardCharsets.UTF_8.name()); +// // TODO public id as well? +// document = dBuilder.parse(inputSource); +// } else { + document = dBuilder.newDocument(); + Element root = document.createElementNS(CrName.CR_NAMESPACE_URI, CrName.ROOT.get().toPrefixedString()); + + for (String prefix : contentTypesManager.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)); + } + + document.appendChild(root); + + // write it + if (path != null) { + try (OutputStream out = Files.newOutputStream(path)) { + writeDom(document, out); + } + } +// } + + DomContentProvider contentProvider = new DomContentProvider(null, document); + addProvider("/", contentProvider); + } catch (DOMException | IOException e) { + throw new IllegalStateException("Cannot init ACR root " + path, e); + } + + } + + public void writeDom(Document document, OutputStream out) throws IOException { + try { + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + DOMSource source = new DOMSource(document); + contentTypesManager.validate(source); + StreamResult result = new StreamResult(out); + transformer.transform(source, result); + } catch (TransformerException e) { + throw new IOException("Cannot write dom", e); + } + } + + /* + * MOUNT MANAGEMENT + */ + + @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); + } } - if (!registeredUri.equals(namespaceURI)) - throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri); - // do nothing if same namespace is already registered + DomContentProvider contentProvider = new DomContentProvider(mountPath, document); + partitions.put(mountPath, contentProvider); + return contentProvider; + } + + @Override + public boolean shouldMount(QName... types) { + if (types.length == 0) + throw new IllegalArgumentException("Types must be provided"); + QName firstType = types[0]; + Set registeredTypes = contentTypesManager.listTypes(); + if (registeredTypes.contains(firstType)) + return true; + return false; } /* @@ -81,17 +277,34 @@ public class CmsContentRepository implements ProvidedRepository { private Subject subject; private Locale locale; + private CompletableFuture closed = new CompletableFuture<>(); + + private CompletableFuture edition; + public CmsContentSession(Subject subject, Locale locale) { this.subject = subject; this.locale = locale; } + public void close() { + closed.complete(this); + } + + @Override + public CompletionStage onClose() { + return closed.minimalCompletionStage(); + } + @Override public Content get(String path) { Map.Entry entry = partitions.floorEntry(path); + if (entry == null) + throw new IllegalArgumentException("No entry provider found for " + path); String mountPath = entry.getKey(); ContentProvider provider = entry.getValue(); String relativePath = path.substring(mountPath.length()); + if (relativePath.length() > 0 && relativePath.charAt(0) == '/') + relativePath = relativePath.substring(1); return provider.get(CmsContentSession.this, mountPath, relativePath); } @@ -110,30 +323,76 @@ public class CmsContentRepository implements ProvidedRepository { 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 findNamespace(String prefix) { - return prefixes.get(prefix); + public String getNamespaceURI(String prefix) { + return NamespaceUtils.getNamespaceURI((p) -> contentTypesManager.getPrefixes().get(p), prefix); } @Override - public Set findPrefixes(String namespaceURI) { - Set res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI)) - .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()); + public Iterator getPrefixes(String namespaceURI) { + return NamespaceUtils.getPrefixes( + (ns) -> contentTypesManager.getPrefixes().entrySet().stream().filter(e -> e.getValue().equals(ns)) + .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), + namespaceURI); + } - return res; + @Override + public CompletionStage edit(Consumer work) { + edition = CompletableFuture.supplyAsync(() -> { + work.accept(this); + return this; + }).thenApply((s) -> { + // TODO optimise + for (ContentProvider provider : partitions.values()) { + if (provider instanceof DomContentProvider) { + ((DomContentProvider) provider).persist(s); + } + } + return s; + }); + return edition.minimalCompletionStage(); } @Override - public 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); + public boolean isEditing() { + return edition != null && !edition.isDone(); } +// @Override +// public String findNamespace(String prefix) { +// return prefixes.get(prefix); +// } +// +// @Override +// public Set findPrefixes(String namespaceURI) { +// Set res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI)) +// .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()); +// +// return res; +// } +// +// @Override +// public String findPrefix(String namespaceURI) { +// if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX)) +// return CrName.CR_DEFAULT_PREFIX; +// return ProvidedSession.super.findPrefix(namespaceURI); +// } + } }