JCR as an ACR backend
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / CmsContentRepository.java
index 9cd8bd22dd6e8eef8670a45fada48cf5a8037f5f..6d17ea87dd4d6fc463e01cfcee3f75ca0c061549 100644 (file)
@@ -1,38 +1,97 @@
 package org.argeo.cms.acr;
 
-import java.security.AccessController;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+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.stream.Collectors;
 
 import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.validation.Schema;
 
 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.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.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.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 
+/**
+ * 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<String, ContentProvider> partitions = new TreeMap<>();
 
        // TODO synchronize ?
        private NavigableMap<String, String> prefixes = new TreeMap<>();
 
+       private Schema schema;
+
+       private CmsContentSession systemSession;
+
+       private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
+
        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);
+
+               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,12 +109,31 @@ 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) {
@@ -69,6 +147,68 @@ public class CmsContentRepository implements ProvidedRepository {
                // 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(schema);
+
+                       DocumentBuilder dBuilder = factory.newDocumentBuilder();
+                       dBuilder.setErrorHandler(new ErrorHandler() {
+
+                               @Override
+                               public void warning(SAXParseException exception) throws SAXException {
+                               }
+
+                               @Override
+                               public void fatalError(SAXParseException exception) throws SAXException {
+                               }
+
+                               @Override
+                               public void error(SAXParseException exception) throws SAXException {
+                                       log.error(exception);
+
+                               }
+                       });
+
+                       Document document;
+                       if (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.ROOT.getNamespaceURI(),
+//                                             CrName.ROOT.get().toPrefixedString());
+                               Element root = document.createElement(CrName.ROOT.get().toPrefixedString());
+                               // root.setAttribute("xmlns", "");
+                               root.setAttribute("xmlns:" + CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
+                               document.appendChild(root);
+
+                               // write it
+                               TransformerFactory transformerFactory = TransformerFactory.newInstance();
+                               Transformer transformer = transformerFactory.newTransformer();
+                               DOMSource source = new DOMSource(document);
+                               try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
+                                       StreamResult result = new StreamResult(writer);
+                                       transformer.transform(source, result);
+                               }
+                       }
+
+                       DomContentProvider contentProvider = new DomContentProvider(document);
+                       addProvider("/", contentProvider);
+               } catch (DOMException | ParserConfigurationException | SAXException | IOException
+                               | TransformerFactoryConfigurationError | TransformerException e) {
+                       throw new IllegalStateException("Cannot init ACR root " + path, e);
+               }
+
+       }
+
        /*
         * NAMESPACE CONTEXT
         */
@@ -81,11 +221,22 @@ public class CmsContentRepository implements ProvidedRepository {
                private Subject subject;
                private Locale locale;
 
+               private CompletableFuture<ProvidedSession> closed = new CompletableFuture<>();
+
                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);
@@ -115,24 +266,35 @@ public class CmsContentRepository implements ProvidedRepository {
                 */
 
                @Override
-               public String findNamespace(String prefix) {
-                       return prefixes.get(prefix);
+               public String getNamespaceURI(String prefix) {
+                       return NamespaceUtils.getNamespaceURI((p) -> prefixes.get(p), 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;
+               public Iterator<String> getPrefixes(String namespaceURI) {
+                       return NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream().filter(e -> e.getValue().equals(ns))
+                                       .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), namespaceURI);
                }
 
-               @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);
-               }
+//             @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);
+//             }
 
        }