From: Mathieu Baudier Date: Wed, 8 Jun 2022 08:28:40 +0000 (+0200) Subject: ACR compatible with Android. X-Git-Tag: v2.3.10~197 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=eb4cc3db3bf141c229f0f7ff929daff108bee6d2;p=lgpl%2Fargeo-commons.git ACR compatible with Android. --- diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java index 5dd37f15b..b32ae3020 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java @@ -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 index a1a37abb3..000000000 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java +++ /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 implements ProvidedContent { - - /* - * ATTRIBUTES OPERATIONS - */ - protected abstract Iterable keys(); - - protected abstract void removeAttr(QName key); - - @Override - public Set> entrySet() { - Set> 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 Optional> getMultiple(QName key, Class clss) { - Object value = get(key); - if (value == null) - return null; - if (value instanceof List) { - try { - List res = (List) value; - return Optional.of(res); - } catch (ClassCastException e) { - List 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 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 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> { - - @Override - public Iterator> iterator() { - final Iterator keys = keys().iterator(); - Iterator> it = new Iterator>() { - - QName key = null; - - @Override - public boolean hasNext() { - return keys.hasNext(); - } - - @Override - public Entry next() { - key = keys.next(); - // TODO check type - Optional value = get(key, Object.class); - assert !value.isEmpty(); - AbstractMap.SimpleEntry 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 it = keys().iterator(); it.hasNext();) { - count++; - } - return count; - } - - } -} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java index 1c88441f7..96a09a91b 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java @@ -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 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 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 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 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 index 9d6ca8dc2..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/SystemLogger.java +++ /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 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 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 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 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 diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index da3354059..2ca0f4722 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -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 index 000000000..b614a14cb --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java @@ -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 implements ProvidedContent { + + /* + * ATTRIBUTES OPERATIONS + */ + protected abstract Iterable keys(); + + protected abstract void removeAttr(QName key); + + @Override + public Set> entrySet() { + Set> 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 Optional> getMultiple(QName key, Class clss) { + Object value = get(key); + if (value == null) + return null; + if (value instanceof List) { + try { + List res = (List) value; + return Optional.of(res); + } catch (ClassCastException e) { + List 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 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 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> { + + @Override + public Iterator> iterator() { + final Iterator keys = keys().iterator(); + Iterator> it = new Iterator>() { + + QName key = null; + + @Override + public boolean hasNext() { + return keys.hasNext(); + } + + @Override + public Entry next() { + key = keys.next(); + // TODO check type + Optional value = get(key, Object.class); + assert !value.isEmpty(); + AbstractMap.SimpleEntry 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 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 index 000000000..382c432b5 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java @@ -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 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 registeredTypes = typesManager.listTypes(); + if (registeredTypes.contains(firstType)) + return true; + return false; + } + + MountManager getMountManager() { + return mountManager; + } + + TypesManager getTypesManager() { + return typesManager; + } + +} 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 1e6108ec3..c2d6b21e4 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -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 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 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 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()); } } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java index 318080509..5e5fb272c 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java @@ -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 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 index 000000000..c44200243 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java @@ -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()); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java index f9077f0a6..c5c3fd128 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java @@ -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> 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? diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java index c6266ee4f..078cb50a8 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java @@ -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; diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java index e17384b94..59f9f450d 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java @@ -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)); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java index bfea129b1..b4931220b 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -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;