ACR compatible with Android.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 8 Jun 2022 08:28:40 +0000 (10:28 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 8 Jun 2022 08:28:40 +0000 (10:28 +0200)
14 files changed:
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java
org.argeo.api.cms/src/org/argeo/api/cms/SystemLogger.java [deleted file]
org.argeo.cms/bnd.bnd
org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java

index 5dd37f15be00eb9b922f93b2b4781917dab816f0..b32ae302085a059bf67c67ddfcca25c173bc9f4a 100644 (file)
@@ -28,10 +28,10 @@ import javax.xml.transform.stream.StreamSource;
 
 import org.argeo.api.acr.Content;
 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.AbstractContent;
 import org.argeo.cms.acr.ContentUtils;
 import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrException;
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java
deleted file mode 100644 (file)
index a1a37ab..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-package org.argeo.api.acr.spi;
-
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.CrName;
-
-/** Partial reference implementation of a {@link ProvidedContent}. */
-public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
-
-       /*
-        * ATTRIBUTES OPERATIONS
-        */
-       protected abstract Iterable<QName> keys();
-
-       protected abstract void removeAttr(QName key);
-
-       @Override
-       public Set<Entry<QName, Object>> entrySet() {
-               Set<Entry<QName, Object>> result = new AttrSet();
-               return result;
-       }
-
-       @Override
-       public Class<?> getType(QName key) {
-               return String.class;
-       }
-
-       @Override
-       public boolean isMultiple(QName key) {
-               return false;
-       }
-
-       @SuppressWarnings("unchecked")
-       @Override
-       public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
-               Object value = get(key);
-               if (value == null)
-                       return null;
-               if (value instanceof List) {
-                       try {
-                               List<A> res = (List<A>) value;
-                               return Optional.of(res);
-                       } catch (ClassCastException e) {
-                               List<A> res = new ArrayList<>();
-                               List<?> lst = (List<?>) value;
-                               try {
-                                       for (Object o : lst) {
-                                               A item = (A) o;
-                                               res.add(item);
-                                       }
-                                       return Optional.of(res);
-                               } catch (ClassCastException e1) {
-                                       return Optional.empty();
-                               }
-                       }
-               } else {// singleton
-                       try {
-                               A res = (A) value;
-                               return Optional.of(Collections.singletonList(res));
-                       } catch (ClassCastException e) {
-                               return Optional.empty();
-                       }
-               }
-       }
-
-       /*
-        * CONTENT OPERATIONS
-        */
-
-       @Override
-       public String getPath() {
-               List<Content> ancestors = new ArrayList<>();
-               collectAncestors(ancestors, this);
-               StringBuilder path = new StringBuilder();
-               for (Content c : ancestors) {
-                       QName name = c.getName();
-                       // FIXME
-                       if (!CrName.ROOT.get().equals(name))
-                               path.append('/').append(name);
-               }
-               return path.toString();
-       }
-
-       private void collectAncestors(List<Content> ancestors, Content content) {
-               if (content == null)
-                       return;
-               ancestors.add(0, content);
-               collectAncestors(ancestors, content.getParent());
-       }
-
-       /*
-        * UTILITIES
-        */
-       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
-               // check whether clss is Object.class
-               return clss.isAssignableFrom(Object.class);
-       }
-
-       @Override
-       public String toString() {
-               return "content " + getPath();
-       }
-
-       /*
-        * SUB CLASSES
-        */
-
-       class AttrSet extends AbstractSet<Entry<QName, Object>> {
-
-               @Override
-               public Iterator<Entry<QName, Object>> iterator() {
-                       final Iterator<QName> keys = keys().iterator();
-                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
-
-                               QName key = null;
-
-                               @Override
-                               public boolean hasNext() {
-                                       return keys.hasNext();
-                               }
-
-                               @Override
-                               public Entry<QName, Object> next() {
-                                       key = keys.next();
-                                       // TODO check type
-                                       Optional<?> value = get(key, Object.class);
-                                       assert !value.isEmpty();
-                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
-                                       return entry;
-                               }
-
-                               @Override
-                               public void remove() {
-                                       if (key != null) {
-                                               AbstractContent.this.removeAttr(key);
-                                       } else {
-                                               throw new IllegalStateException("Iteration has not started");
-                                       }
-                               }
-
-                       };
-                       return it;
-               }
-
-               @Override
-               public int size() {
-
-                       int count = 0;
-                       for (Iterator<QName> it = keys().iterator(); it.hasNext();) {
-                               count++;
-                       }
-                       return count;
-               }
-
-       }
-}
index 1c88441f77a5b2febaa1bf43840760458f5f780f..96a09a91b1bfdb496cdf857a0ad4af3d2171aca0 100644 (file)
@@ -1,17 +1,45 @@
 package org.argeo.api.cms;
 
 import java.lang.System.Logger;
+import java.text.MessageFormat;
 import java.util.Objects;
 import java.util.function.Supplier;
 
-import org.argeo.api.cms.SystemLogger.Level;
-
 /**
- * A Commons Logging / SLF4J style logging utilities wrapping a standard Java
- * platform {@link Logger}.
+ * A Commons Logging / SLF4J style logging utilities usually wrapping a standard
+ * Java platform {@link Logger}, but which can fallback to other mechanism, if a
+ * system logger is not available.
  */
 public interface CmsLog {
-       SystemLogger getLogger();
+       /*
+        * SYSTEM LOGGER STYLE METHODS
+        */
+       boolean isLoggable(Level level);
+
+       void log(Level level, Supplier<String> msgSupplier, Throwable thrown);
+
+       void log(Level level, String msg, Throwable thrown);
+
+       void log(Level level, String format, Object... params);
+
+       default void log(Level level, String msg) {
+               log(level, msg, (Throwable) null);
+       }
+
+       default void log(Level level, Supplier<String> msgSupplier) {
+               log(level, msgSupplier, (Throwable) null);
+       }
+
+       default void log(Level level, Object obj) {
+               Objects.requireNonNull(obj);
+               log(level, obj.toString());
+       }
+
+       /*
+        * SLF4j / COMMONS LOGGING STYLE METHODS
+        */
+       @Deprecated
+       CmsLog getLogger();
 
        default boolean isDebugEnabled() {
                return getLogger().isLoggable(Level.DEBUG);
@@ -173,6 +201,30 @@ public interface CmsLog {
                getLogger().log(Level.ERROR, format, arguments);
        }
 
+       /**
+        * Exact mapping of ${java.lang.System.Logger.Level}, in case it is not
+        * available.
+        */
+       public static enum Level {
+               ALL(Integer.MIN_VALUE), //
+               TRACE(400), //
+               DEBUG(500), //
+               INFO(800), //
+               WARNING(900), //
+               ERROR(1000), //
+               OFF(Integer.MAX_VALUE); //
+
+               final int severity;
+
+               private Level(int severity) {
+                       this.severity = severity;
+               }
+
+               public final int getSeverity() {
+                       return severity;
+               }
+       }
+
        /*
         * STATIC UTILITIES
         */
@@ -182,13 +234,123 @@ public interface CmsLog {
        }
 
        static CmsLog getLog(String name) {
-               SystemLogger logger;
+               if (isSystemLoggerAvailable) {
+                       return new SystemCmsLog(name);
+               } else { // typically Android
+                       return new FallBackCmsLog();
+               }
+       }
+
+       static final boolean isSystemLoggerAvailable = isSystemLoggerAvailable();
+
+       static boolean isSystemLoggerAvailable() {
                try {
-                       logger = new RealSystemLogger(name);
+                       Logger logger = System.getLogger(CmsLog.class.getName());
+                       logger.log(java.lang.System.Logger.Level.TRACE, () -> "System logger is available.");
+                       return true;
                } catch (NoSuchMethodError | NoClassDefFoundError e) {// Android
-                       logger = new FallBackSystemLogger();
+                       return false;
                }
-               return new LoggerWrapper(logger);
+       }
+}
+
+/**
+ * Uses {@link System.Logger}, should be used on proper implementations of the
+ * Java platform.
+ */
+class SystemCmsLog implements CmsLog {
+       private final Logger logger;
+
+       SystemCmsLog(String name) {
+               logger = System.getLogger(name);
+       }
+
+       @Override
+       public boolean isLoggable(Level level) {
+               return logger.isLoggable(convertSystemLevel(level));
+       }
+
+       @Override
+       public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+               logger.log(convertSystemLevel(level), msgSupplier, thrown);
+       }
+
+       @Override
+       public void log(Level level, String msg, Throwable thrown) {
+               logger.log(convertSystemLevel(level), msg, thrown);
+       }
+
+       java.lang.System.Logger.Level convertSystemLevel(Level level) {
+               switch (level.severity) {
+               case Integer.MIN_VALUE:
+                       return java.lang.System.Logger.Level.ALL;
+               case 400:
+                       return java.lang.System.Logger.Level.TRACE;
+               case 500:
+                       return java.lang.System.Logger.Level.DEBUG;
+               case 800:
+                       return java.lang.System.Logger.Level.INFO;
+               case 900:
+                       return java.lang.System.Logger.Level.WARNING;
+               case 1000:
+                       return java.lang.System.Logger.Level.ERROR;
+               case Integer.MAX_VALUE:
+                       return java.lang.System.Logger.Level.OFF;
+               default:
+                       throw new IllegalArgumentException("Unexpected value: " + level.severity);
+               }
+       }
+
+       @Override
+       public void log(Level level, String format, Object... params) {
+               logger.log(convertSystemLevel(level), format, params);
        }
 
+       @Override
+       public CmsLog getLogger() {
+               return this;
+       }
+};
+
+/** Dummy fallback for non-standard platforms such as Android. */
+class FallBackCmsLog implements CmsLog {
+       @Override
+       public boolean isLoggable(Level level) {
+               return level.getSeverity() >= 800;// INFO and higher
+       }
+
+       @Override
+       public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+               if (isLoggable(level))
+                       if (thrown != null || level.getSeverity() >= 900) {
+                               System.err.println(msgSupplier.get());
+                               thrown.printStackTrace();
+                       } else {
+                               System.out.println(msgSupplier.get());
+                       }
+       }
+
+       @Override
+       public void log(Level level, String msg, Throwable thrown) {
+               if (isLoggable(level))
+                       if (thrown != null || level.getSeverity() >= 900) {
+                               System.err.println(msg);
+                               thrown.printStackTrace();
+                       } else {
+                               System.out.println(msg);
+                       }
+       }
+
+       @Override
+       public void log(Level level, String format, Object... params) {
+               if (format == null)
+                       return;
+               String msg = MessageFormat.format(format, params);
+               log(level, msg);
+       }
+
+       @Override
+       public CmsLog getLogger() {
+               return this;
+       }
 }
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/SystemLogger.java b/org.argeo.api.cms/src/org/argeo/api/cms/SystemLogger.java
deleted file mode 100644 (file)
index 9d6ca8d..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.argeo.api.cms;
-
-import java.lang.System.Logger;
-import java.util.Objects;
-import java.util.function.Supplier;
-
-/** Workaround because Android does not support {@link Logger}. */
-abstract class SystemLogger {
-       public enum Level {
-               ALL(Integer.MIN_VALUE, java.lang.System.Logger.Level.ALL), //
-               TRACE(400, java.lang.System.Logger.Level.TRACE), //
-               DEBUG(500, java.lang.System.Logger.Level.DEBUG), //
-               INFO(800, java.lang.System.Logger.Level.INFO), //
-               WARNING(900, java.lang.System.Logger.Level.WARNING), //
-               ERROR(1000, java.lang.System.Logger.Level.ERROR), //
-               OFF(Integer.MAX_VALUE, java.lang.System.Logger.Level.OFF); //
-
-               private final int severity;
-               private java.lang.System.Logger.Level systemLevel;
-
-               private Level(int severity, java.lang.System.Logger.Level systemLevel) {
-                       this.severity = severity;
-                       this.systemLevel = systemLevel;
-               }
-
-               public final int getSeverity() {
-                       return severity;
-               }
-
-               public java.lang.System.Logger.Level getSystemLevel() {
-                       return systemLevel;
-               }
-       }
-
-       public boolean isLoggable(Level level) {
-               return false;
-       }
-
-       public abstract void log(Level level, Supplier<String> msgSupplier, Throwable thrown);
-
-       public abstract void log(Level level, String msg, Throwable thrown);
-
-       public void log(Level level, String msg) {
-               log(level, msg, (Throwable) null);
-       }
-
-       public void log(Level level, Supplier<String> msgSupplier) {
-               log(level, msgSupplier, (Throwable) null);
-       }
-
-       public void log(Level level, Object obj) {
-               Objects.requireNonNull(obj);
-               log(level, obj.toString());
-       }
-
-       public void log(Level level, String format, Object... params) {
-               // FIXME implement it
-               String msg = null;
-               log(level, msg);
-       }
-
-}
-
-/** A trivial implementation wrapping a platform logger. */
-class LoggerWrapper implements CmsLog {
-       private final SystemLogger logger;
-
-       LoggerWrapper(SystemLogger logger) {
-               this.logger = logger;
-       }
-
-       @Override
-       public SystemLogger getLogger() {
-               return logger;
-       }
-
-}
-
-class RealSystemLogger extends SystemLogger {
-       final Logger logger;
-
-       RealSystemLogger(String name) {
-               logger = System.getLogger(name);
-       }
-
-       @Override
-       public boolean isLoggable(Level level) {
-               return logger.isLoggable(convertSystemLevel(level));
-       }
-
-       @Override
-       public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
-               logger.log(convertSystemLevel(level), msgSupplier, thrown);
-       }
-
-       @Override
-       public void log(Level level, String msg, Throwable thrown) {
-               logger.log(convertSystemLevel(level), msg, thrown);
-       }
-
-       System.Logger.Level convertSystemLevel(Level level) {
-               return level.getSystemLevel();
-       }
-};
-
-class FallBackSystemLogger extends SystemLogger {
-       @Override
-       public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
-               if (isLoggable(level))
-                       if (thrown != null) {
-                               System.err.println(msgSupplier.get());
-                               thrown.printStackTrace();
-                       } else {
-                               System.out.println(msgSupplier.get());
-                       }
-       }
-
-       @Override
-       public void log(Level level, String msg, Throwable thrown) {
-               if (isLoggable(level))
-                       if (thrown != null) {
-                               System.err.println(msg);
-                               thrown.printStackTrace();
-                       } else {
-                               System.out.println(msg);
-                       }
-       }
-}
\ No newline at end of file
index da33540591598dca23d6f3a16f055c7ef9bb4200..2ca0f4722c294e7e579545891e7c93221a1c4fc8 100644 (file)
@@ -5,6 +5,7 @@ org.argeo.osgi.transaction, \
 org.apache.commons.httpclient.cookie;resolution:=optional,\
 !com.sun.security.jgss,\
 org.osgi.*;version=0.0.0,\
+org.apache.xerces.jaxp;resolution:=optional,\
 *
 
 Service-Component:\
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java
new file mode 100644 (file)
index 0000000..b614a14
--- /dev/null
@@ -0,0 +1,168 @@
+package org.argeo.cms.acr;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ProvidedContent;
+
+/** Partial reference implementation of a {@link ProvidedContent}. */
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+       protected abstract Iterable<QName> keys();
+
+       protected abstract void removeAttr(QName key);
+
+       @Override
+       public Set<Entry<QName, Object>> entrySet() {
+               Set<Entry<QName, Object>> result = new AttrSet();
+               return result;
+       }
+
+       @Override
+       public Class<?> getType(QName key) {
+               return String.class;
+       }
+
+       @Override
+       public boolean isMultiple(QName key) {
+               return false;
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
+               Object value = get(key);
+               if (value == null)
+                       return null;
+               if (value instanceof List) {
+                       try {
+                               List<A> res = (List<A>) value;
+                               return Optional.of(res);
+                       } catch (ClassCastException e) {
+                               List<A> res = new ArrayList<>();
+                               List<?> lst = (List<?>) value;
+                               try {
+                                       for (Object o : lst) {
+                                               A item = (A) o;
+                                               res.add(item);
+                                       }
+                                       return Optional.of(res);
+                               } catch (ClassCastException e1) {
+                                       return Optional.empty();
+                               }
+                       }
+               } else {// singleton
+                       try {
+                               A res = (A) value;
+                               return Optional.of(Collections.singletonList(res));
+                       } catch (ClassCastException e) {
+                               return Optional.empty();
+                       }
+               }
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+
+       @Override
+       public String getPath() {
+               List<Content> ancestors = new ArrayList<>();
+               collectAncestors(ancestors, this);
+               StringBuilder path = new StringBuilder();
+               for (Content c : ancestors) {
+                       QName name = c.getName();
+                       // FIXME
+                       if (!CrName.ROOT.get().equals(name))
+                               path.append('/').append(name);
+               }
+               return path.toString();
+       }
+
+       private void collectAncestors(List<Content> ancestors, Content content) {
+               if (content == null)
+                       return;
+               ancestors.add(0, content);
+               collectAncestors(ancestors, content.getParent());
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+               // check whether clss is Object.class
+               return clss.isAssignableFrom(Object.class);
+       }
+
+//     @Override
+//     public String toString() {
+//             return "content " + getPath();
+//     }
+
+       /*
+        * SUB CLASSES
+        */
+
+       class AttrSet extends AbstractSet<Entry<QName, Object>> {
+
+               @Override
+               public Iterator<Entry<QName, Object>> iterator() {
+                       final Iterator<QName> keys = keys().iterator();
+                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
+
+                               QName key = null;
+
+                               @Override
+                               public boolean hasNext() {
+                                       return keys.hasNext();
+                               }
+
+                               @Override
+                               public Entry<QName, Object> next() {
+                                       key = keys.next();
+                                       // TODO check type
+                                       Optional<?> value = get(key, Object.class);
+                                       assert !value.isEmpty();
+                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
+                                       return entry;
+                               }
+
+                               @Override
+                               public void remove() {
+                                       if (key != null) {
+                                               AbstractContent.this.removeAttr(key);
+                                       } else {
+                                               throw new IllegalStateException("Iteration has not started");
+                                       }
+                               }
+
+                       };
+                       return it;
+               }
+
+               @Override
+               public int size() {
+
+                       int count = 0;
+                       for (Iterator<QName> it = keys().iterator(); it.hasNext();) {
+                               count++;
+                       }
+                       return count;
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java
new file mode 100644 (file)
index 0000000..382c432
--- /dev/null
@@ -0,0 +1,201 @@
+package org.argeo.cms.acr;
+
+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.Set;
+
+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 org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+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.cms.CmsLog;
+import org.argeo.cms.acr.xml.DomContentProvider;
+import org.argeo.cms.acr.xml.DomUtils;
+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}.
+ */
+public abstract class AbstractContentRepository implements ProvidedRepository {
+       private final static CmsLog log = CmsLog.getLog(AbstractContentRepository.class);
+
+       private final MountManager mountManager;
+       private final TypesManager typesManager;
+
+       private CmsContentSession systemSession;
+
+       // utilities
+       /** Should be used only to copy source and results. */
+       private TransformerFactory identityTransformerFactory = TransformerFactory.newInstance();
+
+       public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path";
+
+       public AbstractContentRepository() {
+               // types
+               typesManager = new TypesManager();
+               typesManager.init();
+               Set<QName> types = typesManager.listTypes();
+               if (log.isTraceEnabled())
+                       for (QName type : types) {
+                               log.trace(type + " - " + typesManager.getAttributeTypes(type));
+                       }
+
+               systemSession = newSystemSession();
+
+               // mounts
+               mountManager = new MountManager(systemSession);
+
+       }
+
+       protected abstract CmsContentSession newSystemSession();
+
+       public void start() {
+       }
+
+       public void stop() {
+
+       }
+
+       /*
+        * REPOSITORY
+        */
+
+       public void addProvider(ContentProvider provider) {
+               mountManager.addStructuralContentProvider(provider);
+       }
+
+       public void registerTypes(String prefix, String namespaceURI, String schemaSystemId) {
+               typesManager.registerTypes(prefix, namespaceURI, schemaSystemId);
+       }
+
+       /*
+        * FACTORIES
+        */
+       public void initRootContentProvider(Path path) {
+               try {
+//                     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+//                     factory.setNamespaceAware(true);
+//                     factory.setXIncludeAware(true);
+//                     factory.setSchema(contentTypesManager.getSchema());
+//
+                       DocumentBuilder dBuilder = typesManager.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 : typesManager.getPrefixes().keySet()) {
+//                             root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
+//                                             contentTypesManager.getPrefixes().get(prefix));
+                               DomUtils.addNamespace(root, prefix, typesManager.getPrefixes().get(prefix));
+                       }
+
+                       document.appendChild(root);
+
+                       // write it
+                       if (path != null) {
+                               try (OutputStream out = Files.newOutputStream(path)) {
+                                       writeDom(document, out);
+                               }
+                       }
+//                     }
+
+                       String mountPath = "/";
+                       DomContentProvider contentProvider = new DomContentProvider(mountPath, 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 = identityTransformerFactory.newTransformer();
+                       transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+                       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+                       DOMSource source = new DOMSource(document);
+                       typesManager.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();
+               // 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(path, document);
+                       return contentProvider;
+               });
+       }
+
+       @Override
+       public boolean shouldMount(QName... types) {
+               if (types.length == 0)
+                       return false;
+               QName firstType = types[0];
+               Set<QName> registeredTypes = typesManager.listTypes();
+               if (registeredTypes.contains(firstType))
+                       return true;
+               return false;
+       }
+
+       MountManager getMountManager() {
+               return mountManager;
+       }
+
+       TypesManager getTypesManager() {
+               return typesManager;
+       }
+
+}
index 1e6108ec3a71246dd26284f3dc37480fc2c8e112..c2d6b21e406f61a5930e43f235de6fa8e7c2e42b 100644 (file)
@@ -1,98 +1,27 @@
 package org.argeo.cms.acr;
 
-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.Locale;
 import java.util.Map;
-import java.util.Set;
 
 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 org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentSession;
-import org.argeo.api.acr.CrName;
-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.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.
+ * Multi-session {@link ProvidedRepository}, integrated with a CMS.
  */
-public class CmsContentRepository implements ProvidedRepository {
-       private final static CmsLog log = CmsLog.getLog(CmsContentRepository.class);
-
-       private final MountManager mountManager;
-       private final TypesManager typesManager;
-
-       private CmsContentSession systemSession;
+public class CmsContentRepository extends AbstractContentRepository {
 
        private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
 
-       // utilities
-       private TransformerFactory transformerFactory = TransformerFactory.newInstance();
-
-       public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path";
-
-       public CmsContentRepository() {
-               // types
-               typesManager = new TypesManager();
-               typesManager.init();
-               Set<QName> types = typesManager.listTypes();
-//             for (QName type : types) {
-//                     log.debug(type + " - " + typesManager.getAttributeTypes(type));
-//             }
-
-               systemSession = newSystemSession();
-
-               // mounts
-               mountManager = new MountManager(systemSession);
-
-       }
-
-       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(this, loginContext.getSubject(), Locale.getDefault());
-       }
-
-       public void start() {
-       }
-
-       public void stop() {
-
-       }
-
        /*
         * REPOSITORY
         */
@@ -118,126 +47,17 @@ public class CmsContentRepository implements ProvidedRepository {
                return contentSession;
        }
 
-       public void addProvider(ContentProvider provider) {
-               mountManager.addStructuralContentProvider(provider);
-       }
-
-       public void registerTypes(String prefix, String namespaceURI, String schemaSystemId) {
-               typesManager.registerTypes(prefix, namespaceURI, schemaSystemId);
-       }
-
-       /*
-        * FACTORIES
-        */
-       public void initRootContentProvider(Path path) {
-               try {
-//                     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-//                     factory.setNamespaceAware(true);
-//                     factory.setXIncludeAware(true);
-//                     factory.setSchema(contentTypesManager.getSchema());
-//
-                       DocumentBuilder dBuilder = typesManager.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 : typesManager.getPrefixes().keySet()) {
-//                             root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
-//                                             contentTypesManager.getPrefixes().get(prefix));
-                               DomUtils.addNamespace(root, prefix, typesManager.getPrefixes().get(prefix));
-                       }
-
-                       document.appendChild(root);
-
-                       // write it
-                       if (path != null) {
-                               try (OutputStream out = Files.newOutputStream(path)) {
-                                       writeDom(document, out);
-                               }
-                       }
-//                     }
-
-                       String mountPath = "/";
-                       DomContentProvider contentProvider = new DomContentProvider(mountPath, 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 {
+       @Override
+       protected CmsContentSession newSystemSession() {
+               LoginContext loginContext;
                try {
-                       Transformer transformer = transformerFactory.newTransformer();
-                       transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
-                       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-
-                       DOMSource source = new DOMSource(document);
-                       typesManager.validate(source);
-                       StreamResult result = new StreamResult(out);
-                       transformer.transform(source, result);
-               } catch (TransformerException e) {
-                       throw new IOException("Cannot write dom", e);
+                       loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName());
+                       loginContext.login();
+               } catch (LoginException e1) {
+                       throw new RuntimeException("Could not login as data admin", e1);
+               } finally {
                }
-       }
-
-       /*
-        * MOUNT MANAGEMENT
-        */
-
-       @Override
-       public ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types) {
-               String mountPath = mountPoint.getPath();
-               // 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(path, document);
-                       return contentProvider;
-               });
-       }
-
-       @Override
-       public boolean shouldMount(QName... types) {
-               if (types.length == 0)
-                       return false;
-               QName firstType = types[0];
-               Set<QName> registeredTypes = typesManager.listTypes();
-               if (registeredTypes.contains(firstType))
-                       return true;
-               return false;
-       }
-
-       MountManager getMountManager() {
-               return mountManager;
-       }
-
-       TypesManager getTypesManager() {
-               return typesManager;
+               return new CmsContentSession(this, loginContext.getSubject(), Locale.getDefault());
        }
 
 }
index 318080509b02ab624e3bf18c3feb124a7f8817db..5e5fb272c24aed96d9ca8d3126f56a3a0964ef15 100644 (file)
@@ -23,7 +23,7 @@ import org.argeo.cms.acr.xml.DomContentProvider;
 
 /** Implements {@link ProvidedSession}. */
 class CmsContentSession implements ProvidedSession {
-       final private CmsContentRepository contentRepository;
+       final private AbstractContentRepository contentRepository;
 
        private Subject subject;
        private Locale locale;
@@ -34,7 +34,7 @@ class CmsContentSession implements ProvidedSession {
 
        private Set<ContentProvider> modifiedProviders = new TreeSet<>();
 
-       public CmsContentSession(CmsContentRepository contentRepository, Subject subject, Locale locale) {
+       public CmsContentSession(AbstractContentRepository contentRepository, Subject subject, Locale locale) {
                this.contentRepository = contentRepository;
                this.subject = subject;
                this.locale = locale;
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java
new file mode 100644 (file)
index 0000000..c442002
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.cms.acr;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.spi.ProvidedRepository;
+
+/**
+ * A standalone {@link ProvidedRepository} with a single {@link Subject} (which
+ * also provides the system session).
+ */
+public class SingleUserContentRepository extends AbstractContentRepository {
+       private final Subject subject;
+       private final Locale locale;
+
+       // the single session
+       private CmsContentSession contentSession;
+
+       public SingleUserContentRepository(Subject subject) {
+               this(subject, Locale.getDefault());
+
+               initRootContentProvider(null);
+       }
+
+       public SingleUserContentRepository(Subject subject, Locale locale) {
+               Objects.requireNonNull(subject);
+               Objects.requireNonNull(locale);
+
+               this.subject = subject;
+               this.locale = locale;
+       }
+
+       @Override
+       public void start() {
+               Objects.requireNonNull(subject);
+               Objects.requireNonNull(locale);
+
+               super.start();
+               if (contentSession != null)
+                       throw new IllegalStateException("Repository is already started, stop it first.");
+               contentSession = new CmsContentSession(this, subject, locale);
+       }
+
+       @Override
+       public void stop() {
+               if (contentSession != null)
+                       contentSession.close();
+               contentSession = null;
+               super.stop();
+       }
+
+       @Override
+       public ContentSession get(Locale locale) {
+               if (!this.locale.equals(locale))
+                       throw new UnsupportedOperationException("This repository does not support multi-locale sessions");
+               return contentSession;
+       }
+
+       @Override
+       public ContentSession get() {
+               return contentSession;
+       }
+
+       @Override
+       protected CmsContentSession newSystemSession() {
+               return new CmsContentSession(this, subject, Locale.getDefault());
+       }
+
+}
index f9077f0a6da9414b016caa25d0a6142c461cb376..c5c3fd12833235a56cd956d881c3b46cc96d9c07 100644 (file)
@@ -10,6 +10,7 @@ import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
+import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -22,6 +23,7 @@ import javax.xml.validation.Validator;
 
 import org.apache.xerces.impl.xs.XSImplementationImpl;
 import org.apache.xerces.impl.xs.util.StringListImpl;
+import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
 import org.apache.xerces.xs.StringList;
 import org.apache.xerces.xs.XSAttributeDeclaration;
 import org.apache.xerces.xs.XSAttributeUse;
@@ -58,14 +60,16 @@ class TypesManager {
 
        // cached
        private Schema schema;
-       DocumentBuilderFactory documentBuilderFactory;
+       private DocumentBuilderFactory documentBuilderFactory;
        private XSModel xsModel;
        private SortedMap<QName, Map<QName, CrAttributeType>> types;
 
        private boolean validating = true;
 
+       private final static boolean limited = true;
+
        public TypesManager() {
-               schemaFactory = SchemaFactory.newDefaultInstance();
+               schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 
                // types
                types = new TreeMap<>((qn1, qn2) -> {
@@ -122,11 +126,14 @@ class TypesManager {
                        schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
 
                        // document builder factory
-                       documentBuilderFactory = DocumentBuilderFactory.newInstance();
+                       // we force usage of Xerces for predictability
+                       documentBuilderFactory = limited ? DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl();
                        documentBuilderFactory.setNamespaceAware(true);
-                       documentBuilderFactory.setXIncludeAware(true);
-                       documentBuilderFactory.setSchema(getSchema());
-                       documentBuilderFactory.setValidating(validating);
+                       if (!limited) {
+                               documentBuilderFactory.setXIncludeAware(true);
+                               documentBuilderFactory.setSchema(getSchema());
+                               documentBuilderFactory.setValidating(validating);
+                       }
 
                        // XS model
                        // TODO use JVM implementation?
index c6266ee4fc2d8fd489be7171f9dcac104341d3ed..078cb50a8d38f28280385b7bf9e90f48c2c5aada 100644 (file)
@@ -26,10 +26,10 @@ import org.argeo.api.acr.ContentName;
 import org.argeo.api.acr.ContentResourceException;
 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.AbstractContent;
 import org.argeo.cms.acr.ContentUtils;
 import org.argeo.util.FsUtils;
 
index e17384b942162d7cefbde3fde1fdb0682ad3674f..59f9f450dec129dda9044f17472d23506b919c05 100644 (file)
@@ -12,7 +12,6 @@ import java.util.NavigableMap;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 
-import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentResourceException;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.NamespaceUtils;
@@ -41,6 +40,8 @@ public class FsContentProvider implements ContentProvider {
                try {
                        UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath,
                                        UserDefinedFileAttributeView.class);
+                       if(udfav==null)
+                               return;
                        for (String name : udfav.list()) {
                                if (name.startsWith(XMLNS_)) {
                                        ByteBuffer buf = ByteBuffer.allocate(udfav.size(name));
index bfea129b11110a7561a9c3b38a1828851b2e58c1..b4931220b2b8077979ab172f0ee5de53915fba6f 100644 (file)
@@ -13,9 +13,9 @@ import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
-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.AbstractContent;
 import org.argeo.cms.acr.ContentUtils;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Document;