From: Mathieu Baudier Date: Fri, 29 Sep 2023 16:17:56 +0000 (+0200) Subject: Work on supporting outpout stream for binary X-Git-Tag: v2.3.16~6 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-jcr.git;a=commitdiff_plain;h=6debb1965a73605ef3c5f012e3781260f23a61a8 Work on supporting outpout stream for binary --- 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 index 149ef3a..3cebe44 100644 --- 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 @@ -3,22 +3,28 @@ 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.CompletableFuture; import java.util.concurrent.ForkJoinPool; +import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; @@ -93,13 +99,27 @@ public class JcrContent extends AbstractContent { @SuppressWarnings("unchecked") @Override public Optional get(QName key, Class clss) { - 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.checkedOut.equals(key)) { - try { + 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(); @@ -107,11 +127,7 @@ public class JcrContent extends AbstractContent { return Optional.empty(); // FIXME return URI return (Optional) Optional.of(new Object()); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } else if (DName.checkedIn.equals(key)) { - try { + } 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(); @@ -119,16 +135,16 @@ public class JcrContent extends AbstractContent { return Optional.empty(); // FIXME return URI return (Optional) Optional.of(new Object()); - } catch (RepositoryException e) { - throw new JcrException(e); } - } - 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); + 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 @@ -152,9 +168,11 @@ public class JcrContent extends AbstractContent { // TODO convert standard names if (property.getName().equals(Property.JCR_CREATED)) name = DName.creationdate.qName(); - if (property.getName().equals(Property.JCR_LAST_MODIFIED)) + else if (property.getName().equals(Property.JCR_LAST_MODIFIED)) name = DName.getlastmodified.qName(); - if (property.getName().equals(Property.JCR_IS_CHECKED_OUT)) { + 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(); } @@ -203,7 +221,7 @@ public class JcrContent extends AbstractContent { } } catch (RepositoryException e) { throw new JcrException( - "Cannot check multiplicityof property " + p + " of " + jcrPath + " in " + jcrWorkspace, e); + "Cannot check multiplicity of property " + p + " of " + jcrPath + " in " + jcrWorkspace, e); } } @@ -271,28 +289,67 @@ public class JcrContent extends AbstractContent { @Override public Content add(QName name, QName... classes) { - if (classes.length > 0) { - QName primaryType = classes[0]; - Node node = openForEdit(); - Node child = Jcr.addNode(node, name.toString(), primaryType.toString()); - for (int i = 1; i < classes.length; i++) { - try { + try { + Node child; + if (classes.length > 0) { + QName primaryType = classes[0]; + Node node = openForEdit(); + child = Jcr.addNode(node, name.toString(), primaryType.toString()); + + for (int i = 1; i < classes.length; i++) child.addMixin(classes[i].toString()); - } catch (RepositoryException e) { - throw new JcrException("Cannot add child to " + getJcrNode(), e); + + if (NtType.file.qName().equals(primaryType)) { + // TODO optimise when we have a proper save mechanism + Node content = child.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); +// Binary binary; +// try (InputStream in = new ByteArrayInputStream(new byte[0])) { +// binary = content.getSession().getValueFactory().createBinary(in); +// content.setProperty(Property.JCR_DATA, binary); +// } catch (IOException e) { +// throw new UncheckedIOException(e); +// } + child.getSession().save(); } + } else { + child = Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED); } + return new JcrContent(getSession(), provider, jcrWorkspace, child.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot add child to " + jcrPath + " in " + jcrWorkspace, e); + } + } - } else { - Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED); + @Override + public Content add(QName name, Map attrs, QName... classes) { + if (attrs.containsKey(DName.getcontenttype.qName())) { + List lst = new ArrayList<>(Arrays.asList(classes)); + lst.add(0, NtType.file.qName()); + classes = lst.toArray(new QName[lst.size()]); } - return null; + if (attrs.containsKey(DName.collection.qName())) { + List lst = Arrays.asList(classes); + lst.add(0, NtType.folder.qName()); + classes = lst.toArray(new QName[lst.size()]); + } + Content child = add(name, classes); + child.putAll(attrs); + return child; } @Override public void remove() { Node node = openForEdit(); Jcr.remove(node); + saveJcrSession(); + } + + private void saveJcrSession() { + try { + getJcrSession().save(); + } catch (RepositoryException e) { + throw new JcrException("Cannot persist " + jcrPath + " in " + jcrWorkspace, e); + } } @Override @@ -306,22 +363,57 @@ public class JcrContent extends AbstractContent { throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e); } } - + saveJcrSession(); } @Override public Object put(QName key, Object value) { + Objects.requireNonNull(value, "Value cannot be null"); + Node node = openForEdit(); try { - String property = NamespaceUtils.toFullyQualified(key); - Node node = openForEdit(); + if (DName.checkedIn.equals(key) || DName.checkedOut.equals(key)) + throw new IllegalArgumentException( + key + " cannot be set, use the openForEdit/freeze methods of the related content provider."); + Object old = null; - if (node.hasProperty(property)) { - old = convertSingleValue(node.getProperty(property).getValue()); + String property; + if (DName.creationdate.equals(key)) + property = Property.JCR_CREATED; + else if (DName.getlastmodified.equals(key)) + property = Property.JCR_LAST_MODIFIED; + else if (DName.getcontenttype.equals(key)) { + if (!node.isNodeType(NodeType.NT_FILE)) + throw new IllegalStateException(DName.getcontenttype + " can only be set on a file"); + Node content = node.getNode(Node.JCR_CONTENT); + old = Jcr.get(content, Property.JCR_MIMETYPE); + if (old != null && Jcr.hasProperty(content, Property.JCR_ENCODING)) + old = old + ";encoding=" + Jcr.get(content, Property.JCR_ENCODING); + String[] str = value.toString().split(";"); + String mimeType = str[0].trim(); + String encoding = null; + if (str.length > 1) { + value = str[0].trim(); + String[] eq = str[1].split("="); + assert eq.length == 2; + if ("encoding".equals(eq[0].trim())) + encoding = eq[1]; + } + content.setProperty(Property.JCR_MIMETYPE, mimeType); + if (encoding != null) + content.setProperty(Property.JCR_ENCODING, encoding); + property = null; + } else + property = NamespaceUtils.toFullyQualified(key); + + if (property != null) { + if (node.hasProperty(property)) { + old = convertSingleValue(node.getProperty(property).getValue()); + } + Value newValue = convertSingleObject(node.getSession().getValueFactory(), value); + node.setProperty(property, newValue); } - Value newValue = convertSingleObject(node.getSession().getValueFactory(), value); - node.setProperty(property, newValue); // FIXME proper edition - node.getSession().save(); + saveJcrSession(); return old; } catch (RepositoryException e) { throw new JcrException("Cannot set property " + key + " on " + jcrPath + " in " + jcrWorkspace, e); @@ -344,7 +436,7 @@ public class JcrContent extends AbstractContent { node.addMixin(nodeType.getName()); } // FIXME proper edition - node.getSession().save(); + saveJcrSession(); } catch (RepositoryException e) { throw new JcrException( "Cannot add content classes " + contentClass + " to " + jcrPath + " in " + jcrWorkspace, e); @@ -414,15 +506,50 @@ public class JcrContent extends AbstractContent { @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 { + try { + if (InputStream.class.isAssignableFrom(clss)) { + Node node = getJcrNode(); + if (Jcr.isNodeType(node, NodeType.NT_FILE)) { return (C) JcrUtils.getFileAsStream(node); - } catch (RepositoryException e) { - throw new JcrException("Cannot open " + jcrPath + " in workspace " + jcrWorkspace, e); + } + } else if (OutputStream.class.isAssignableFrom(clss)) { + Node node = getJcrNode(); + if (Jcr.isNodeType(node, NodeType.NT_FILE)) { + Node content = node.getNode(Node.JCR_CONTENT); + PipedInputStream in = new PipedInputStream(); + ValueFactory valueFactory = getJcrSession().getValueFactory(); + CompletableFuture done = CompletableFuture.runAsync(() -> { + try { + Binary binary = valueFactory.createBinary(in); + content.setProperty(Property.JCR_DATA, binary); + saveJcrSession(); + } catch (RepositoryException e) { + throw new JcrException( + "Cannot create binary in " + jcrPath + " in workspace " + jcrWorkspace, e); + } + }); + PipedOutputStream out = new PipedOutputStream(in) { + + @Override + public void close() throws IOException { + super.flush(); + super.close(); + done.join(); +// Binary binary = done.join(); +// try { +// content.setProperty(Property.JCR_DATA, binary); +// } catch (RepositoryException e) { +// throw new JcrException( +// "Cannot write binary to " + jcrPath + " in workspace " + jcrWorkspace, e); +// } +// saveJcrSession(); + } + }; + return (C) out; } } + } catch (RepositoryException e) { + throw new JcrException("Cannot open " + jcrPath + " in workspace " + jcrWorkspace, e); } return super.open(clss); } @@ -537,6 +664,8 @@ public class JcrContent extends AbstractContent { // primary node type NodeType primaryType = context.getPrimaryNodeType(); res.add(nodeTypeToQName(primaryType)); + if (primaryType.getName().equals(NodeType.NT_FOLDER)) + res.add(DName.collection.qName()); Set secondaryTypes = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR); for (NodeType mixinType : context.getMixinNodeTypes()) { diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/MixInType.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/MixInType.java new file mode 100644 index 0000000..6481751 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/MixInType.java @@ -0,0 +1,17 @@ +package org.argeo.cms.jcr.acr; + +import org.argeo.api.acr.QNamed; + +public enum MixInType implements QNamed { + mimeType; + + @Override + public String getNamespace() { + return JcrContentNamespace.JCR_MIX.getNamespaceURI(); + } + + @Override + public String getDefaultPrefix() { + return JcrContentNamespace.JCR_MIX.getDefaultPrefix(); + } +} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/NtType.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/NtType.java new file mode 100644 index 0000000..56fd498 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/NtType.java @@ -0,0 +1,17 @@ +package org.argeo.cms.jcr.acr; + +import org.argeo.api.acr.QNamed; + +public enum NtType implements QNamed { + file, folder; + + @Override + public String getNamespace() { + return JcrContentNamespace.JCR_NT.getNamespaceURI(); + } + + @Override + public String getDefaultPrefix() { + return JcrContentNamespace.JCR_NT.getDefaultPrefix(); + } +}