X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms.jcr%2Fsrc%2Forg%2Fargeo%2Fcms%2Fjcr%2Facr%2FJcrContent.java;fp=org.argeo.cms.jcr%2Fsrc%2Forg%2Fargeo%2Fcms%2Fjcr%2Facr%2FJcrContent.java;h=a4af35bc671437ad6b3d9d587817181a9ab7a480;hb=bce03099b0d2f1758e7a3d74fba339d0200924d5;hp=0000000000000000000000000000000000000000;hpb=0ea2992e5f3803cd98b5981f27274faa922bc199;p=gpl%2Fargeo-jcr.git diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java new file mode 100644 index 0000000..a4af35b --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java @@ -0,0 +1,387 @@ +package org.argeo.cms.jcr.acr; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ForkJoinPool; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.nodetype.NodeType; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.NamespaceUtils; +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; +import org.argeo.jcr.JcrUtils; + +/** A JCR {@link Node} accessed as {@link Content}. */ +public class JcrContent extends AbstractContent { +// private Node jcrNode; + + private JcrContentProvider provider; + + private String jcrWorkspace; + private String jcrPath; + + protected JcrContent(ProvidedSession session, JcrContentProvider provider, String jcrWorkspace, String jcrPath) { + super(session); + this.provider = provider; + this.jcrWorkspace = jcrWorkspace; + this.jcrPath = jcrPath; + } + + @Override + public QName getName() { + String name = Jcr.getName(getJcrNode()); + if (name.equals("")) {// root + String mountPath = provider.getMountPath(); + name = ContentUtils.getParentPath(mountPath)[1]; + // name = Jcr.getWorkspaceName(getJcrNode()); + } + return NamespaceUtils.parsePrefixedName(provider, name); + } + + @SuppressWarnings("unchecked") + @Override + public Optional get(QName key, Class clss) { + if (isDefaultAttrTypeRequested(clss)) { + return Optional.of((A) get(getJcrNode(), key.toString())); + } + return Optional.of((A) Jcr.get(getJcrNode(), key.toString())); + } + + @Override + public Iterator iterator() { + try { + return new JcrContentIterator(getJcrNode().getNodes()); + } catch (RepositoryException e) { + throw new JcrException("Cannot list children of " + getJcrNode(), e); + } + } + + @Override + protected Iterable keys() { + try { + Set keys = new HashSet<>(); + for (PropertyIterator propertyIterator = getJcrNode().getProperties(); propertyIterator.hasNext();) { + Property property = propertyIterator.nextProperty(); + // TODO convert standard names + // TODO skip technical properties + QName name = NamespaceUtils.parsePrefixedName(provider, property.getName()); + keys.add(name); + } + return keys; + } catch (RepositoryException e) { + throw new JcrException("Cannot list properties of " + getJcrNode(), e); + } + } + + public Node getJcrNode() { + try { + // TODO caching? + return provider.getJcrSession(getSession(), jcrWorkspace).getNode(jcrPath); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve " + jcrPath + " from workspace " + jcrWorkspace, e); + } + } + + /** Cast to a standard Java object. */ + static Object get(Node node, String property) { + try { + Property p = node.getProperty(property); + if (p.isMultiple()) { + Value[] values = p.getValues(); + List lst = new ArrayList<>(); + for (Value value : values) { + lst.add(convertSingleValue(value)); + } + return lst; + } else { + Value value = node.getProperty(property).getValue(); + return convertSingleValue(value); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot cast value from " + property + " of node " + node, e); + } + } + + static Object convertSingleValue(Value value) throws RepositoryException { + switch (value.getType()) { + case PropertyType.STRING: + return value.getString(); + case PropertyType.DOUBLE: + return (Double) value.getDouble(); + case PropertyType.LONG: + return (Long) value.getLong(); + case PropertyType.BOOLEAN: + return (Boolean) value.getBoolean(); + case PropertyType.DATE: + Calendar calendar = value.getDate(); + return calendar.toInstant(); + case PropertyType.BINARY: + throw new IllegalArgumentException("Binary is not supported as an attribute"); + default: + return value.getString(); + } + } + + class JcrContentIterator implements Iterator { + private final NodeIterator nodeIterator; + // we keep track in order to be able to delete it + private JcrContent current = null; + + protected JcrContentIterator(NodeIterator nodeIterator) { + this.nodeIterator = nodeIterator; + } + + @Override + public boolean hasNext() { + return nodeIterator.hasNext(); + } + + @Override + public Content next() { + current = new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode())); + return current; + } + + @Override + public void remove() { + if (current != null) { + Jcr.remove(current.getJcrNode()); + } + } + + } + + @Override + public String getPath() { + try { + // Note: it is important to to use the default way (recursing through parents), + // since the session may not have access to parent nodes + return ContentUtils.ROOT_SLASH + jcrWorkspace + getJcrNode().getPath(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get depth of " + getJcrNode(), e); + } + } + + @Override + public int getDepth() { + try { + return getJcrNode().getDepth() + 1; + } catch (RepositoryException e) { + throw new JcrException("Cannot get depth of " + getJcrNode(), e); + } + } + + @Override + public Content getParent() { + if (Jcr.isRoot(getJcrNode())) // root + return null; + return new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getParentPath(getJcrNode())); + } + + @Override + public Content add(QName name, QName... classes) { + if (classes.length > 0) { + QName primaryType = classes[0]; + Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString()); + for (int i = 1; i < classes.length; i++) { + try { + child.addMixin(classes[i].toString()); + } catch (RepositoryException e) { + throw new JcrException("Cannot add child to " + getJcrNode(), e); + } + } + + } else { + Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED); + } + return null; + } + + @Override + public void remove() { + Jcr.remove(getJcrNode()); + } + + @Override + protected void removeAttr(QName key) { + Property property = Jcr.getProperty(getJcrNode(), key.toString()); + if (property != null) { + try { + property.remove(); + } catch (RepositoryException e) { + throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e); + } + } + + } + + boolean exists() { + try { + return provider.getJcrSession(getSession(), jcrWorkspace).itemExists(jcrPath); + } catch (RepositoryException e) { + throw new JcrException("Cannot check whether " + jcrPath + " exists", e); + } + } + + /* + * ADAPTERS + */ + @SuppressWarnings("unchecked") + public A adapt(Class clss) { + if (Source.class.isAssignableFrom(clss)) { +// try { + PipedInputStream in = new PipedInputStream(); + + ForkJoinPool.commonPool().execute(() -> { + try (PipedOutputStream out = new PipedOutputStream(in)) { + provider.getJcrSession(getSession(), jcrWorkspace).exportDocumentView(jcrPath, out, true, false); + out.flush(); + } catch (IOException | RepositoryException e) { + throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e); + } + + }); + return (A) new StreamSource(in); +// } catch (IOException e) { +// throw new RuntimeException("Cannot adapt " + JcrContent.this + " to " + clss, e); +// } + } else + + return super.adapt(clss); + } + + @SuppressWarnings("unchecked") + @Override + public C open(Class clss) throws IOException, IllegalArgumentException { + if (InputStream.class.isAssignableFrom(clss)) { + Node node = getJcrNode(); + if (Jcr.isNodeType(node, NodeType.NT_FILE)) { + try { + return (C) JcrUtils.getFileAsStream(node); + } catch (RepositoryException e) { + throw new JcrException("Cannot open " + jcrPath + " in workspace " + jcrWorkspace, e); + } + } + } + return super.open(clss); + } + + @Override + public ContentProvider getProvider() { + return provider; + } + + @Override + public String getSessionLocalId() { + try { + return getJcrNode().getIdentifier(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get identifier for " + getJcrNode(), e); + } + } + + /* + * TYPING + */ + @Override + public List getContentClasses() { + try { +// Node node = getJcrNode(); +// List res = new ArrayList<>(); +// res.add(nodeTypeToQName(node.getPrimaryNodeType())); +// for (NodeType mixin : node.getMixinNodeTypes()) { +// res.add(nodeTypeToQName(mixin)); +// } +// return res; + Node context = getJcrNode(); + + List res = new ArrayList<>(); + // primary node type + NodeType primaryType = context.getPrimaryNodeType(); + res.add(nodeTypeToQName(primaryType)); + + Set secondaryTypes = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR); + for (NodeType mixinType : context.getMixinNodeTypes()) { + secondaryTypes.add(nodeTypeToQName(mixinType)); + } + for (NodeType superType : primaryType.getDeclaredSupertypes()) { + secondaryTypes.add(nodeTypeToQName(superType)); + } + // mixins + for (NodeType mixinType : context.getMixinNodeTypes()) { + for (NodeType superType : mixinType.getDeclaredSupertypes()) { + secondaryTypes.add(nodeTypeToQName(superType)); + } + } +// // entity type +// if (context.isNodeType(EntityType.entity.get())) { +// if (context.hasProperty(EntityNames.ENTITY_TYPE)) { +// String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString(); +// if (byType.containsKey(entityTypeName)) { +// types.add(entityTypeName); +// } +// } +// } + res.addAll(secondaryTypes); + return res; + } catch (RepositoryException e) { + throw new JcrException("Cannot list node types from " + getJcrNode(), e); + } + } + + private QName nodeTypeToQName(NodeType nodeType) { + String name = nodeType.getName(); + return QName.valueOf(name); + } + + @Override + public int getSiblingIndex() { + return Jcr.getIndex(getJcrNode()); + } + + /* + * STATIC UTLITIES + */ + public static Content nodeToContent(Node node) { + if (node == null) + return null; + try { + ProvidedSession contentSession = (ProvidedSession) node.getSession() + .getAttribute(ProvidedSession.class.getName()); + if (contentSession == null) + throw new IllegalArgumentException( + "Cannot adapt " + node + " to content, because it was not loaded from a content session"); + return contentSession.get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot adapt " + node + " to a content", e); + } + } + +}