--- /dev/null
+package org.argeo.cms.jcr.acr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMSource;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/** Utilities around integration between JCR and ACR. */
+public class JcrContentUtils {
+ private final static CmsLog log = CmsLog.getLog(JcrContentUtils.class);
+
+ public static void copyFiles(Node folder, Content collection) {
+ try {
+ nodes: for (NodeIterator it = folder.getNodes(); it.hasNext();) {
+ Node node = it.nextNode();
+ String name = node.getName();
+ if (node.isNodeType(NodeType.NT_FILE)) {
+ Content file = collection.anyOrAddChild(new ContentName(name));
+ try (InputStream in = JcrUtils.getFileAsStream(node)) {
+ file.write(InputStream.class).complete(in);
+ }
+ } else if (node.isNodeType(NodeType.NT_FOLDER)) {
+ Content subCol = collection.add(name, CrName.collection.qName());
+ copyFiles(node, subCol);
+ } else {
+ QName qName = NamespaceUtils.parsePrefixedName(name);
+ if (NamespaceUtils.hasNamespace(qName)) {
+ if (node.getIndex() > 1) {
+ log.warn("Same name siblings not supported, skipping " + node);
+ continue nodes;
+ }
+ Content content = collection.add(qName, qName);
+ Source source = toSource(node);
+ ((ProvidedContent) content).getSession().edit((s) -> {
+ ((ProvidedSession) s).notifyModification((ProvidedContent) content);
+ content.write(Source.class).complete(source);
+// ContentUtils.traverse(content,
+// (c, depth) -> ContentUtils.print(c, System.out, depth, false));
+ }).toCompletableFuture().join();
+
+ } else {
+ // ignore
+ log.debug(() -> "Ignored " + node);
+ continue nodes;
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot copy files from " + folder + " to " + collection, e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy files from " + folder + " to " + collection, e);
+ }
+ }
+
+ private static Source toSource(Node node) {
+ try (PipedInputStream in = new PipedInputStream();) {
+
+ CompletableFuture<Document> toDo = CompletableFuture.supplyAsync(() -> {
+ try {
+ DocumentBuilder documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder();
+ return documentBuilder.parse(in);
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+ throw new RuntimeException("Cannot parse", e);
+ }
+ });
+
+ // TODO optimise
+ try (PipedOutputStream out = new PipedOutputStream(in)) {
+ node.getSession().exportDocumentView(node.getPath(), out, true, false);
+ } catch (IOException | RepositoryException e) {
+ throw new RuntimeException("Cannot export " + node + " in workspace " + Jcr.getWorkspaceName(node), e);
+ }
+ Document document = toDo.get();
+ cleanJcrDom(document);
+ return new DOMSource(document);
+ } catch (IOException | InterruptedException | ExecutionException e1) {
+ throw new RuntimeException("Cannot parse", e1);
+ }
+
+ }
+
+ static final String JCR_NAMESPACE_URI = "http://www.jcp.org/jcr/1.0";
+
+ public static void cleanJcrDom(Document document) {
+ Element documentElement = document.getDocumentElement();
+ Set<String> namespaceUris = new HashSet<>();
+ cleanJcrDom(documentElement, namespaceUris);
+
+ // remove unused namespaces
+ NamedNodeMap attrs = documentElement.getAttributes();
+ Set<Attr> toRemove = new HashSet<>();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Attr attr = (Attr) attrs.item(i);
+// log.debug("Check "+i+" " + attr);
+ String prefix = attr.getPrefix();
+ if (prefix != null && prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
+ String namespaceUri = attr.getValue();
+ if (!namespaceUris.contains(namespaceUri)) {
+ toRemove.add(attr);
+ //log.debug("Removing "+i+" " + namespaceUri);
+ }
+ }
+ }
+ for(Attr attr:toRemove)
+ documentElement.removeAttributeNode(attr);
+
+ }
+
+ private static void cleanJcrDom(Element element, Set<String> namespaceUris) {
+ NodeList children = element.getElementsByTagName("*");
+ for (int i = 0; i < children.getLength(); i++) {
+ Element child = (Element) children.item(i);
+ if (!namespaceUris.contains(child.getNamespaceURI()))
+ namespaceUris.add(child.getNamespaceURI());
+ cleanJcrDom(child, namespaceUris);
+ }
+
+ NamedNodeMap attrs = element.getAttributes();
+ attributes: for (int i = 0; i < attrs.getLength(); i++) {
+ Attr attr = (Attr) attrs.item(i);
+ String namespaceUri = attr.getNamespaceURI();
+ if (namespaceUri == null)
+ continue attributes;
+ if (JCR_NAMESPACE_URI.equals(namespaceUri)) {
+ element.removeAttributeNode(attr);
+ continue attributes;
+ }
+ if (!namespaceUris.contains(namespaceUri))
+ namespaceUris.add(attr.getNamespaceURI());
+
+ }
+
+ }
+
+ /** singleton */
+ private JcrContentUtils() {
+ }
+
+}
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import org.argeo.api.acr.spi.ProvidedRepository;
import org.argeo.api.cms.CmsLog;
import org.argeo.cms.jcr.CmsJcrUtils;
import org.argeo.jcr.Jcr;
private UserAdmin userAdmin;
private WorkTransaction userTransaction;
+ private ProvidedRepository contentRepository;
+
public void init() {
makeSureRolesExists(getRequiredRoles());
configureStandardRoles();
this.userTransaction = userTransaction;
}
+ public void setContentRepository(ProvidedRepository contentRepository) {
+ this.contentRepository = contentRepository;
+ }
+
+ protected ProvidedRepository getContentRepository() {
+ return contentRepository;
+ }
+
+
}
import java.io.Closeable;
import java.io.IOException;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
throw new UnsupportedOperationException("Cannot write content " + this + " as " + clss.getName());
}
+ /*
+ * CHILDREN
+ */
+
+ default boolean hasChild(QName name) {
+ for (Content child : this) {
+ if (child.getName().equals(name))
+ return true;
+ }
+ return false;
+ }
+
+ default Content anyOrAddChild(QName name, QName... classes) {
+ Content child = anyChild(name);
+ if (child != null)
+ return child;
+ return this.add(name, classes);
+ }
+
+ /** Any child with this name, or null if there is none */
+ default Content anyChild(QName name) {
+ for (Content child : this) {
+ if (child.getName().equals(name))
+ return child;
+ }
+ return null;
+ }
+
/*
* CONVENIENCE METHODS
*/
};
- /** singleton */
- private NamespaceUtils() {
+ public static boolean hasNamespace(QName qName) {
+ return !qName.getNamespaceURI().equals(XMLConstants.NULL_NS_URI);
}
/*
return prefixes.iterator();
}
+ /** singleton */
+ private NamespaceUtils() {
+ }
+
}
void registerView(String uid, Object view);
void addOnCloseCallback(Consumer<CmsSession> onClose);
+
+ public static boolean hasCmsSession(Subject subject) {
+ return !subject.getPrivateCredentials(CmsSessionId.class).isEmpty();
+ }
}
import java.security.Principal;
+import javax.security.auth.Subject;
+
/** Allows to modify any data. */
public final class DataAdminPrincipal implements Principal {
private final String name = CmsConstants.ROLE_DATA_ADMIN;
return name.toString();
}
+ public static boolean isDataAdmin(Subject subject) {
+ return !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
+ }
}
TypesManager getTypesManager() {
return typesManager;
}
+
+ CmsContentSession getSystemSession() {
+ return systemSession;
+ }
+
+
}
import org.argeo.api.cms.CmsAuth;
import org.argeo.api.cms.CmsSession;
import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.DataAdminPrincipal;
import org.argeo.api.uuid.UuidFactory;
import org.argeo.cms.auth.CurrentUser;
import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.util.CurrentSubject;
/**
* Multi-session {@link ProvidedRepository}, integrated with a CMS.
@Override
public ContentSession get(Locale locale) {
- // Subject subject = Subject.getSubject(AccessController.getContext());
+ if (!CmsSession.hasCmsSession(CurrentSubject.current())) {
+ if (DataAdminPrincipal.isDataAdmin(CurrentSubject.current())) {
+ // TODO open multiple data admin sessions?
+ return getSystemSession();
+ }
+ throw new IllegalStateException("Caller must be authenticated");
+ }
+
CmsSession cmsSession = CurrentUser.getCmsSession();
CmsContentSession contentSession = userSessions.get(cmsSession);
if (contentSession == null) {
package org.argeo.cms.acr;
+import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
-import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
private CompletableFuture<ContentSession> edition;
- private Set<ContentProvider> modifiedProviders = new TreeSet<>();
+ private Set<ContentProvider> modifiedProviders = new HashSet<>();
private Content sessionRunDir;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
import javax.xml.namespace.QName;
import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.cms.CmsAuth;
import org.argeo.cms.CmsUserManager;
import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.CurrentSubject;
import org.osgi.service.useradmin.Role;
/** Utilities and routines around {@link Content}. */
throw new IllegalArgumentException("Path " + path + " contains //");
}
- /** Singleton. */
- private ContentUtils() {
-
- }
-
public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) {
UserDirectory userDirectory = userManager.getDirectory(role);
String path = CmsContentRepository.DIRECTORY_BASE + SLASH + userDirectory.getName() + SLASH
return content;
}
+ /*
+ * CONSUMER UTILS
+ */
+
+ public static Content createCollections(ContentSession session, String path) {
+ if (session.exists(path)) {
+ Content content = session.get(path);
+ if (!content.isContentClass(CrName.collection.qName())) {
+ throw new IllegalStateException("Content " + path + " already exists, but is not a collection");
+ } else {
+ return content;
+ }
+ } else {
+ String[] parentPath = getParentPath(path);
+ Content parent = createCollections(session, parentPath[0]);
+ Content content = parent.add(parentPath[1], CrName.collection.qName());
+ return content;
+ }
+ }
+
+ public static ContentSession openDataAdminSession(ContentRepository repository) {
+ LoginContext loginContext;
+ try {
+ loginContext = CmsAuth.DATA_ADMIN.newLoginContext();
+ loginContext.login();
+ } catch (LoginException e1) {
+ throw new RuntimeException("Could not login as data admin", e1);
+ } finally {
+ }
+
+ ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(ContentUtils.class.getClassLoader());
+ return CurrentSubject.callAs(loginContext.getSubject(), () -> repository.get());
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentCl);
+ }
+ }
+
+ /** Singleton. */
+ private ContentUtils() {
+
+ }
+
}
if (entry == null)
throw new IllegalArgumentException("No entry provider found for " + path);
String mountPath = entry.getKey();
- if (!path.startsWith(mountPath))
- throw new IllegalArgumentException("Path " + path + " doesn't have a content provider");
+ if (!path.startsWith(mountPath)) {
+ // FIXME make it more robust and find when there is no content provider
+ String[] parent = ContentUtils.getParentPath(path);
+ return findContentProvider(parent[0]);
+ // throw new IllegalArgumentException("Path " + path + " doesn't have a content
+ // provider");
+ }
ContentProvider contentProvider = entry.getValue();
assert mountPath.equals(contentProvider.getMountPath());
return contentProvider;
private XSModel xsModel;
private SortedMap<QName, Map<QName, CrAttributeType>> types;
- private boolean validating = true;
+ private boolean validating = false;
private final static boolean limited = false;
if (xsdSystemId != null) {
sources.add(new StreamSource(xsdSystemId));
reload();
+ log.debug(() -> "Registered types " + namespace + " from " + xsdSystemId);
}
}
try {
validator.validate(source);
} catch (SAXException e) {
- throw new IllegalArgumentException("Provided source is not valid", e);
+ log.error(source + " is not valid ", e);
+ // throw new IllegalArgumentException("Provided source is not valid", e);
}
}
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import javax.xml.namespace.QName;
return provider;
}
+ /*
+ * READ / WRITE
+ */
+ public <A> CompletableFuture<A> write(Class<A> clss) {
+ if (isContentClass(CrName.collection.qName())) {
+ throw new IllegalStateException("Cannot directly write to a collection");
+ }
+ if (InputStream.class.isAssignableFrom(clss)) {
+ CompletableFuture<InputStream> res = new CompletableFuture<>();
+ res.thenAccept((in) -> {
+ try {
+ Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot write to " + path, e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ }
+ return super.write(clss);
+ }
}
import org.argeo.api.acr.ContentResourceException;
import org.argeo.api.acr.CrName;
import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
return new FsContent(session, this, rootPath.resolve(relativePath));
}
- /*
- * NAMESPACE CONTEXT
- */
-
@Override
public boolean exists(ProvidedSession session, String relativePath) {
return Files.exists(rootPath.resolve(relativePath));
}
+ /*
+ * NAMESPACE CONTEXT
+ */
+
@Override
public String getNamespaceURI(String prefix) {
return NamespaceUtils.getNamespaceURI((p) -> prefixes.get(p), prefix);
@Override
public Iterator<String> getPrefixes(String namespaceURI) {
- return NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream().filter(e -> e.getValue().equals(ns))
- .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), namespaceURI);
+ Iterator<String> res = NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream()
+ .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()),
+ namespaceURI);
+ if (!res.hasNext()) {
+ String prefix = RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI);
+ if (prefix != null) {
+ registerPrefix(prefix, namespaceURI);
+ return getPrefixes(namespaceURI);
+ } else {
+ throw new IllegalArgumentException("Unknown namespace " + namespaceURI);
+ }
+ }
+ return res;
}
}
package org.argeo.cms.acr.xml;
import java.nio.CharBuffer;
-import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.CrName;
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.DOMException;
import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
String textContent = element.getTextContent();
CharBuffer buf = CharBuffer.wrap(textContent);
return (A) buf;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ DOMSource source = new DOMSource(element);
+ return (A) source;
}
return super.adapt(clss);
}
element.setTextContent(s);
});
return (CompletableFuture<A>) res;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ CompletableFuture<Source> res = new CompletableFuture<>();
+ res.thenAccept((source) -> {
+ try {
+ Transformer transformer = provider.getTransformerFactory().newTransformer();
+ DocumentFragment documentFragment = element.getOwnerDocument().createDocumentFragment();
+ DOMResult result = new DOMResult(documentFragment);
+ transformer.transform(source, result);
+ // Node parentNode = element.getParentNode();
+ Element resultElement = (Element) documentFragment.getFirstChild();
+ QName resultName = toQName(resultElement);
+ if (!resultName.equals(getName()))
+ throw new IllegalArgumentException(resultName + "+ is not compatible with " + getName());
+
+ // attributes
+ NamedNodeMap attrs = resultElement.getAttributes();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Attr attr2 = (Attr) element.getOwnerDocument().importNode(attrs.item(i), true);
+ element.getAttributes().setNamedItem(attr2);
+ }
+
+ // Move all the children
+ while (element.hasChildNodes()) {
+ element.removeChild(element.getFirstChild());
+ }
+ while (resultElement.hasChildNodes()) {
+ element.appendChild(resultElement.getFirstChild());
+ }
+// parentNode.replaceChild(resultNode, element);
+// element = (Element)resultNode;
+
+ } catch (DOMException | TransformerException e) {
+ throw new RuntimeException("Cannot write to element", e);
+ }
+ });
+ return (CompletableFuture<A>) res;
}
return super.write(clss);
}
import java.util.List;
import javax.xml.namespace.NamespaceContext;
+import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
// TODO centralise in some executor?
private final ThreadLocal<XPath> xPath;
+ private TransformerFactory transformerFactory;
+
private String mountPath;
public DomContentProvider(String mountPath, Document document) {
this.mountPath = mountPath;
this.document = document;
this.document.normalizeDocument();
+
+ transformerFactory = TransformerFactory.newInstance();
+
XPathFactory xPathFactory = XPathFactory.newInstance();
xPath = new ThreadLocal<>() {
return Collections.unmodifiableList(res).iterator();
}
+ TransformerFactory getTransformerFactory() {
+ return transformerFactory;
+ }
+
}
subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class));
return true;
}
+
}