package org.argeo.cms.jcr.acr;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ForkJoinPool;
import javax.jcr.Binary;
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.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeManager;
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.CrAttributeType;
import org.argeo.api.acr.DName;
import org.argeo.api.acr.NamespaceUtils;
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.cms.util.AsyncPipedOutputStream;
import org.argeo.jcr.Jcr;
import org.argeo.jcr.JcrException;
import org.argeo.jcr.JcrUtils;
import org.argeo.jcr.JcrxApi;
/** A JCR {@link Node} accessed as {@link Content}. */
public class JcrContent extends AbstractContent {
private JcrContentProvider provider;
private String jcrWorkspace;
private String jcrPath;
private final boolean isMountBase;
/* OPTIMISATIONS */
/**
* While we want to support thread-safe access, it is very likely that only
* thread and only one sesssion will be used (typically from a single-threaded
* UI). We therefore cache was long as the same thread is calling.
*/
private Thread lastRetrievingThread = null;
private Node cachedNode = null;
private boolean caching = true;
protected JcrContent(ProvidedSession session, JcrContentProvider provider, String jcrWorkspace, String jcrPath) {
super(session);
this.provider = provider;
this.jcrWorkspace = jcrWorkspace;
this.jcrPath = jcrPath;
this.isMountBase = ContentUtils.SLASH_STRING.equals(jcrPath);
}
/*
* READ
*/
@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) {
try {
Node node = getJcrNode();
if (DName.creationdate.equals(key))
key = JcrName.created.qName();
else if (DName.getlastmodified.equals(key))
key = JcrName.lastModified.qName();
else if (DName.getcontenttype.equals(key)) {
String contentType = null;
if (node.isNodeType(NodeType.NT_FILE)) {
Node content = node.getNode(Node.JCR_CONTENT);
if (content.isNodeType(NodeType.MIX_MIMETYPE)) {
contentType = content.getProperty(Property.JCR_MIMETYPE).getString();
if (content.hasProperty(Property.JCR_ENCODING))
contentType = contentType + ";encoding="
+ content.getProperty(Property.JCR_ENCODING).getString();
}
}
if (contentType == null)
contentType = "application/octet-stream";
return CrAttributeType.cast(clss, contentType);
} else if (DName.checkedOut.equals(key)) {
if (!node.hasProperty(Property.JCR_IS_CHECKED_OUT))
return Optional.empty();
boolean isCheckedOut = node.getProperty(Property.JCR_IS_CHECKED_OUT).getBoolean();
if (!isCheckedOut)
return Optional.empty();
// FIXME return URI
return (Optional) Optional.of(new Object());
} else if (DName.checkedIn.equals(key)) {
if (!node.hasProperty(Property.JCR_IS_CHECKED_OUT))
return Optional.empty();
boolean isCheckedOut = node.getProperty(Property.JCR_IS_CHECKED_OUT).getBoolean();
if (isCheckedOut)
return Optional.empty();
// FIXME return URI
return (Optional) Optional.of(new Object());
}
Object value = get(node, key.toString());
if (value instanceof List> lst)
return Optional.of((A) lst);
// TODO check other collections?
return CrAttributeType.cast(clss, value);
} catch (RepositoryException e) {
throw new JcrException(e);
}
}
@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 {
Node node = getJcrNode();
Set keys = new HashSet<>();
for (PropertyIterator propertyIterator = node.getProperties(); propertyIterator.hasNext();) {
Property property = propertyIterator.nextProperty();
QName name = NamespaceUtils.parsePrefixedName(provider, property.getName());
// TODO convert standard names
if (property.getName().equals(Property.JCR_CREATED))
name = DName.creationdate.qName();
else if (property.getName().equals(Property.JCR_LAST_MODIFIED))
name = DName.getlastmodified.qName();
else if (property.getName().equals(Property.JCR_MIMETYPE))
name = DName.getcontenttype.qName();
else if (property.getName().equals(Property.JCR_IS_CHECKED_OUT)) {
boolean isCheckedOut = node.getProperty(Property.JCR_IS_CHECKED_OUT).getBoolean();
name = isCheckedOut ? DName.checkedOut.qName() : DName.checkedIn.qName();
}
// TODO skip technical properties
keys.add(name);
}
return keys;
} catch (RepositoryException e) {
throw new JcrException("Cannot list properties of " + getJcrNode(), e);
}
}
/** Cast to a standard Java object. */
static Object get(Node node, String property) {
try {
if (!node.hasProperty(property))
return null;
Property p = node.getProperty(property);
if (p.isMultiple()) {
Value[] values = p.getValues();
List