Merge tag 'v2.3.18' into testing
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 9 Nov 2023 10:35:29 +0000 (11:35 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 9 Nov 2023 10:35:29 +0000 (11:35 +0100)
13 files changed:
NOTICE
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/BasicSearchToQom.java
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrName.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrSessionAdapter.java
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/MixInType.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/NtType.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java
sdk/argeo-build
sdk/branches/unstable.bnd
swt/org.argeo.cms.jcr.ui/src/org/argeo/cms/ui/CmsUiConstants.java [deleted file]
swt/org.argeo.cms.jcr.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java
swt/org.argeo.cms.jcr.ui/src/org/argeo/cms/ui/widgets/StyledControl.java

diff --git a/NOTICE b/NOTICE
index 6cc89a6e03e07540e5ccdcd5f9b8f4089ac51e6a..1179424fc370ecb437f8fbdcb1cf580205526db7 100644 (file)
--- a/NOTICE
+++ b/NOTICE
@@ -39,6 +39,27 @@ whether to do so. The GNU General Public License gives permission to release a
 modified version without this exception; this exception also makes it possible 
 to release a modified version which carries forward this exception.
 
+# Apache License Permission
+
+Linking Argeo JCR statically or dynamically with other modules is making a 
+combined work based on Argeo JCR. Thus, the terms and conditions of the GNU 
+General Public License cover the whole combination when this license becomes 
+applicable.
+
+In addition, as a special exception, the copyright holders of Argeo JCR give 
+you permission to combine Argeo JCR with any program released under the 
+terms and conditions of the Apache License v2.0 or any later version of this 
+license. You may copy and distribute such a system following the terms of 
+the GNU GPL for Argeo JCR and the licenses of the other code concerned, 
+provided that you include the source code of that other code when and as 
+the GNU GPL requires distribution of source code.
+
+Note that people who make modified versions of Argeo JCR are not obligated 
+to grant this special exception for their modified versions; it is their choice 
+whether to do so. The GNU General Public License gives permission to release a 
+modified version without this exception; this exception also makes it possible 
+to release a modified version which carries forward this exception.
+
 # Java Content Repository API version 2.0 Permission
 
 Linking Argeo JCR statically or dynamically with other modules is making a 
index 99b8392161e34758740b467fc9068a81812b25fa..ab01d7fd33d2e999b2d0f078142a5acdff595292 100644 (file)
@@ -1,8 +1,11 @@
 package org.argeo.cms.jcr.acr;
 
+import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO;
+
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.jcr.Property;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.query.QueryManager;
@@ -15,13 +18,21 @@ import javax.jcr.query.qom.StaticOperand;
 import javax.xml.namespace.QName;
 
 import org.apache.jackrabbit.commons.query.sql2.QOMFormatter;
+import org.argeo.api.acr.DName;
 import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.search.BasicSearch;
 import org.argeo.api.acr.search.Constraint;
 import org.argeo.api.acr.search.ContentFilter;
-import org.argeo.api.acr.search.ContentFilter.Eq;
-import org.argeo.api.acr.search.ContentFilter.IsContentClass;
-import org.argeo.api.acr.search.ContentFilter.Not;
+import org.argeo.api.acr.search.Eq;
+import org.argeo.api.acr.search.Gt;
+import org.argeo.api.acr.search.Gte;
+import org.argeo.api.acr.search.IsContentClass;
+import org.argeo.api.acr.search.IsDefined;
+import org.argeo.api.acr.search.Like;
+import org.argeo.api.acr.search.Lt;
+import org.argeo.api.acr.search.Lte;
+import org.argeo.api.acr.search.Not;
+import org.argeo.api.acr.search.PropertyValueContraint;
 import org.argeo.api.cms.CmsLog;
 
 /** Convert an ACR basic search to a JCR query. */
@@ -31,16 +42,19 @@ class BasicSearchToQom {
        private Session session;
        private QueryManager queryManager;
        private BasicSearch basicSearch;
-       QueryObjectModelFactory factory;
+       private QueryObjectModelFactory factory;
+
+       private String relPath;
 
-       QName contentClass = null;
+       private QName contentClass = null;
 
-       String selectorName = "content";
+       private String selectorName = "content";
 
        public BasicSearchToQom(Session session, BasicSearch basicSearch, String relPath) throws RepositoryException {
                this.session = session;
                this.queryManager = session.getWorkspace().getQueryManager();
                this.basicSearch = basicSearch;
+               this.relPath = relPath;
                factory = queryManager.getQOMFactory();
        }
 
@@ -53,6 +67,10 @@ class BasicSearchToQom {
                if (contentClass == null)
                        throw new IllegalArgumentException("No content class specified");
 
+               if (relPath != null) {
+                       qomConstraint = factory.and(qomConstraint, factory.descendantNode(selectorName, "/" + relPath));
+               }
+
                Selector source = factory.selector(NamespaceUtils.toPrefixedName(contentClass), selectorName);
 
                QueryObjectModel qom = factory.createQuery(source, qomConstraint, null, null);
@@ -64,7 +82,7 @@ class BasicSearchToQom {
        }
 
        private javax.jcr.query.qom.Constraint toQomConstraint(Constraint constraint) throws RepositoryException {
-               javax.jcr.query.qom.Constraint qomConstraint;
+//             javax.jcr.query.qom.Constraint qomConstraint;
                if (constraint instanceof ContentFilter<?> where) {
                        List<Constraint> constraints = new ArrayList<>();
                        for (Constraint c : where.getConstraints()) {
@@ -78,35 +96,81 @@ class BasicSearchToQom {
                        }
 
                        if (constraints.isEmpty()) {
-                               qomConstraint = null;
+                               return null;
                        } else if (constraints.size() == 1) {
-                               qomConstraint = toQomConstraint(constraints.get(0));
+                               return toQomConstraint(constraints.get(0));
                        } else {
                                javax.jcr.query.qom.Constraint currQomConstraint = toQomConstraint(constraints.get(0));
+                               // QOM constraint may be null because only content classes where specified
+                               while (currQomConstraint == null) {
+                                       constraints.remove(0);
+                                       if (constraints.isEmpty())
+                                               return null;
+                                       currQomConstraint = toQomConstraint(constraints.get(0));
+                               }
+                               assert currQomConstraint != null : "currQomConstraint is null : " + constraints.get(0);
                                for (int i = 1; i < constraints.size(); i++) {
                                        Constraint c = constraints.get(i);
-                                       if (where.isUnion()) {
-                                               currQomConstraint = factory.or(currQomConstraint, toQomConstraint(c));
-                                       } else {
-                                               currQomConstraint = factory.and(currQomConstraint, toQomConstraint(c));
+                                       javax.jcr.query.qom.Constraint subQomConstraint = toQomConstraint(c);
+                                       if (subQomConstraint != null) { // isContentClass leads to null QOM constraint
+                                               assert subQomConstraint != null : "subQomConstraint";
+                                               if (where.isUnion()) {
+                                                       currQomConstraint = factory.or(currQomConstraint, subQomConstraint);
+                                               } else {
+                                                       currQomConstraint = factory.and(currQomConstraint, subQomConstraint);
+                                               }
                                        }
                                }
-                               qomConstraint = currQomConstraint;
+                               return currQomConstraint;
                        }
 
-               } else if (constraint instanceof Eq comp) {
-                       DynamicOperand dynamicOperand = factory.propertyValue(selectorName,
-                                       NamespaceUtils.toPrefixedName(comp.getProp()));
+               } else if (constraint instanceof PropertyValueContraint comp) {
+                       QName prop = comp.getProp();
+                       if (DName.creationdate.equals(prop))
+                               prop = JcrName.created.qName();
+                       else if (DName.getlastmodified.equals(prop))
+                               prop = JcrName.lastModified.qName();
+
+                       DynamicOperand dynamicOperand = factory.propertyValue(selectorName, NamespaceUtils.toPrefixedName(prop));
                        // TODO better convert attribute value
                        StaticOperand staticOperand = factory
-                                       .literal(session.getValueFactory().createValue(comp.getValue().toString()));
-                       qomConstraint = factory.comparison(dynamicOperand, QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO,
-                                       staticOperand);
+                                       .literal(JcrContent.convertSingleObject(session.getValueFactory(), comp.getValue()));
+                       javax.jcr.query.qom.Constraint res;
+                       if (comp instanceof Eq)
+                               res = factory.comparison(dynamicOperand, QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO,
+                                               staticOperand);
+                       else if (comp instanceof Lt)
+                               res = factory.comparison(dynamicOperand, QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN,
+                                               staticOperand);
+                       else if (comp instanceof Lte)
+                               res = factory.comparison(dynamicOperand, QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO,
+                                               staticOperand);
+                       else if (comp instanceof Gt)
+                               res = factory.comparison(dynamicOperand, QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN,
+                                               staticOperand);
+                       else if (comp instanceof Gte)
+                               res = factory.comparison(dynamicOperand,
+                                               QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO, staticOperand);
+                       else if (comp instanceof Like)
+                               res = factory.comparison(dynamicOperand, QueryObjectModelConstants.JCR_OPERATOR_LIKE, staticOperand);
+                       else
+                               throw new UnsupportedOperationException("Constraint of type " + comp.getClass() + " is not supported");
+                       return res;
                } else if (constraint instanceof Not not) {
-                       qomConstraint = factory.not(toQomConstraint(not.getNegated()));
+                       return factory.not(toQomConstraint(not.getNegated()));
+               } else if (constraint instanceof IsDefined comp) {
+                       QName prop = comp.getProp();
+                       if (DName.checkedIn.equals(prop) || DName.checkedOut.equals(prop)) {
+                               DynamicOperand dynamicOperand = factory.propertyValue(selectorName, Property.JCR_IS_CHECKED_OUT);
+                               StaticOperand staticOperand = factory
+                                               .literal(session.getValueFactory().createValue(DName.checkedOut.equals(prop)));
+                               return factory.comparison(dynamicOperand, JCR_OPERATOR_EQUAL_TO, staticOperand);
+                       } else {
+                               return factory.propertyExistence(selectorName, NamespaceUtils.toPrefixedName(prop));
+                       }
                } else {
                        throw new IllegalArgumentException("Constraint " + constraint.getClass() + " is not supported");
                }
-               return qomConstraint;
+//             return qomConstraint;
        }
 }
index 9e662ecc96ab8dcbf128c452ba1c1f42acfc661f..880f29eeaebf4c30f87fd850ddd8ab84036b48c1 100644 (file)
@@ -3,22 +3,27 @@ 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;
@@ -36,14 +41,17 @@ 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 {
@@ -56,8 +64,8 @@ public class JcrContent extends AbstractContent {
 
        /* 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
+        * While we want to support thread-safe access, it is very likely that only one
+        * thread and only one session will be used (typically from a single-threaded
         * UI). We therefore cache was long as the same thread is calling.
         */
        private Thread lastRetrievingThread = null;
@@ -91,11 +99,52 @@ public class JcrContent extends AbstractContent {
        @SuppressWarnings("unchecked")
        @Override
        public <A> Optional<A> get(QName key, Class<A> clss) {
-               Object value = get(getJcrNode(), key.toString());
-               if (value instanceof List<?> lst)
-                       return Optional.of((A) lst);
-               // TODO check other collections?
-               return CrAttributeType.cast(clss, value);
+               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<A>) 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<A>) 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
@@ -110,12 +159,25 @@ public class JcrContent extends AbstractContent {
        @Override
        protected Iterable<QName> keys() {
                try {
+                       Node node = getJcrNode();
                        Set<QName> keys = new HashSet<>();
-                       for (PropertyIterator propertyIterator = getJcrNode().getProperties(); propertyIterator.hasNext();) {
+                       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
-                               QName name = NamespaceUtils.parsePrefixedName(provider, property.getName());
                                keys.add(name);
                        }
                        return keys;
@@ -159,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);
                }
        }
 
@@ -202,6 +264,11 @@ public class JcrContent extends AbstractContent {
                return Jcr.getIndex(getJcrNode());
        }
 
+       @Override
+       public String getText() {
+               return JcrxApi.getXmlValue(getJcrNode());
+       }
+
        /*
         * MAP OPTIMISATIONS
         */
@@ -222,28 +289,66 @@ public class JcrContent extends AbstractContent {
 
        @Override
        public Content add(QName name, QName... classes) {
-               if (classes.length > 0) {
-                       QName primaryType = classes[0];
+               try {
                        Node node = openForEdit();
-                       Node child = Jcr.addNode(node, name.toString(), primaryType.toString());
-                       for (int i = 1; i < classes.length; i++) {
-                               try {
+                       Node child;
+                       if (classes.length > 0) {
+                               classes: for (int i = 0; i < classes.length; i++) {
+                                       if (classes[i].equals(DName.collection.qName())) {
+                                               List<QName> lst = new ArrayList<>(Arrays.asList(classes));
+                                               lst.add(0, NtType.folder.qName());
+                                               lst.remove(DName.collection.qName());
+                                               classes = lst.toArray(new QName[lst.size()]);
+                                               break classes;
+                                       }
+                               }
+                               QName primaryType = classes[0];
+                               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
+                                       child.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
                                }
+                       } else {
+                               child = Jcr.addNode(node, name.toString(), NodeType.NT_UNSTRUCTURED);
                        }
+                       saveEditedNode(node);
+                       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<QName, Object> attrs, QName... classes) {
+               if (attrs.containsKey(DName.getcontenttype.qName())) {
+                       List<QName> lst = new ArrayList<>(Arrays.asList(classes));
+                       lst.add(0, NtType.file.qName());
+                       classes = lst.toArray(new QName[lst.size()]);
                }
-               return null;
+
+               Content child = add(name, classes);
+               child.putAll(attrs);
+               return child;
        }
 
        @Override
        public void remove() {
                Node node = openForEdit();
                Jcr.remove(node);
+               saveEditedNode(node);
+       }
+
+       private void saveEditedNode(Node node) {
+               try {
+                       node.getSession().save();
+                       getJcrSession().refresh(true);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot persist " + jcrPath + " in " + jcrWorkspace, e);
+               }
        }
 
        @Override
@@ -257,22 +362,57 @@ public class JcrContent extends AbstractContent {
                                throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
                        }
                }
-
+               saveEditedNode(node);
        }
 
        @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();
+                       saveEditedNode(node);
                        return old;
                } catch (RepositoryException e) {
                        throw new JcrException("Cannot set property " + key + " on " + jcrPath + " in " + jcrWorkspace, e);
@@ -295,7 +435,7 @@ public class JcrContent extends AbstractContent {
                                node.addMixin(nodeType.getName());
                        }
                        // FIXME proper edition
-                       node.getSession().save();
+                       saveEditedNode(node);
                } catch (RepositoryException e) {
                        throw new JcrException(
                                        "Cannot add content classes " + contentClass + " to " + jcrPath + " in " + jcrWorkspace, e);
@@ -365,15 +505,36 @@ public class JcrContent extends AbstractContent {
        @SuppressWarnings("unchecked")
        @Override
        public <C extends Closeable> C open(Class<C> 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();
+//                             System.out.println(node.getSession());
+                               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 = openForEdit();
+//                             System.out.println(node.getSession());
+                               if (Jcr.isNodeType(node, NodeType.NT_FILE)) {
+                                       Node content = node.getNode(Node.JCR_CONTENT);
+                                       AsyncPipedOutputStream out = new AsyncPipedOutputStream();
+
+                                       ValueFactory valueFactory = getJcrSession().getValueFactory();
+                                       out.asyncRead((in) -> {
+                                               try {
+                                                       Binary binary = valueFactory.createBinary(in);
+                                                       content.setProperty(Property.JCR_DATA, binary);
+                                                       saveEditedNode(node);
+                                               } catch (RepositoryException e) {
+                                                       throw new JcrException(
+                                                                       "Cannot create binary in " + jcrPath + " in workspace " + jcrWorkspace, e);
+                                               }
+                                       });
+                                       return (C) out;
                                }
                        }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot open " + jcrPath + " in workspace " + jcrWorkspace, e);
                }
                return super.open(clss);
        }
@@ -488,6 +649,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<QName> secondaryTypes = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
                        for (NodeType mixinType : context.getMixinNodeTypes()) {
@@ -519,7 +682,14 @@ public class JcrContent extends AbstractContent {
         * COMMON UTILITIES
         */
        protected Session getJcrSession() {
-               return provider.getJcrSession(getSession(), jcrWorkspace);
+               Session s = provider.getJcrSession(getSession(), jcrWorkspace);
+//             if (getSession().isEditing())
+//                     try {
+//                             s.refresh(false);
+//                     } catch (RepositoryException e) {
+//                             throw new JcrException("Cannot refresh session", e);
+//                     }
+               return s;
        }
 
        protected Node getJcrNode() {
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrName.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrName.java
new file mode 100644 (file)
index 0000000..de0d1b1
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cms.jcr.acr;
+
+import org.argeo.api.acr.QNamed;
+
+public enum JcrName implements QNamed {
+       created, lastModified, isCheckedOut;
+
+       @Override
+       public String getNamespace() {
+               return JcrContentNamespace.JCR.getNamespaceURI();
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return JcrContentNamespace.JCR.getDefaultPrefix();
+       }
+
+}
index 0fc46b8c9cc734f344ecc2233922a006734d20a5..58a5b77e58b78ec5811a32cca93eceaeb44ef458 100644 (file)
@@ -107,22 +107,35 @@ class JcrSessionAdapter {
        public synchronized Node openForEdit(String workspace, String jcrPath) throws RepositoryException {
                Session session = getWriteSession(workspace);
                Node node = session.getNode(jcrPath);
-               if (node.isNodeType(NodeType.MIX_SIMPLE_VERSIONABLE)) {
-                       VersionManager versionManager = session.getWorkspace().getVersionManager();
-                       if (versionManager.isCheckedOut(jcrPath)) {
+               VersionManager versionManager = session.getWorkspace().getVersionManager();
+
+               Node versionedAncestor = findVersionedAncestor(node);
+               boolean checkedOut = versionManager.isCheckedOut(jcrPath);
+
+               if (versionedAncestor != null) {
+                       if (checkedOut) {
                                if (!checkedOutModified.containsKey(workspace))
                                        checkedOutModified.put(workspace, new TreeSet<>());
-                               checkedOutModified.get(workspace).add(jcrPath);
+                               checkedOutModified.get(workspace).add(versionedAncestor.getPath());
                        } else {
                                if (!checkedInModified.containsKey(workspace))
                                        checkedInModified.put(workspace, new TreeSet<>());
-                               checkedInModified.get(workspace).add(jcrPath);
-                               versionManager.checkout(jcrPath);
+                               checkedInModified.get(workspace).add(versionedAncestor.getPath());
+                               versionManager.checkout(versionedAncestor.getPath());
                        }
                }
                return node;
        }
 
+       private Node findVersionedAncestor(Node node) throws RepositoryException {
+               if (node.isNodeType(NodeType.MIX_SIMPLE_VERSIONABLE))
+                       return node;
+               Node parent = node.getParent();
+               if (parent == null)
+                       return null;
+               return findVersionedAncestor(parent);
+       }
+
        public synchronized Node freeze(String workspace, String jcrPath) throws RepositoryException {
                Session session = getWriteSession(workspace);
                Node node = session.getNode(jcrPath);
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 (file)
index 0000000..6481751
--- /dev/null
@@ -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 (file)
index 0000000..56fd498
--- /dev/null
@@ -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();
+       }
+}
index bdeaac707511d554403fcea465b8fc80780f9de5..b6ee85eef345f6cf8f9ce0e2f521cdbb346c0d44 100644 (file)
@@ -14,6 +14,7 @@ import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 
 import javax.jcr.Binary;
 import javax.jcr.ItemNotFoundException;
@@ -595,6 +596,7 @@ public class Jcr {
         */
        @SuppressWarnings("unchecked")
        public static <T> T getAs(Node node, String property, T defaultValue) {
+               Objects.requireNonNull(defaultValue);
                try {
                        // TODO deal with multiple
                        if (node.hasProperty(property)) {
@@ -619,13 +621,30 @@ public class Jcr {
 
        @SuppressWarnings("unchecked")
        public static <T> T getAs(Node node, String property, Class<T> clss) {
-               if (String.class.isAssignableFrom(clss)) {
-                       return (T) get(node, property);
-               } else if (Long.class.isAssignableFrom(clss)) {
-                       return (T) get(node, property);
-               } else {
-                       throw new IllegalArgumentException("Unsupported format " + clss);
+               try {
+                       Property p = node.getProperty(property);
+                       try {
+                               if (p.isMultiple()) {
+                                       throw new UnsupportedOperationException("Multiple values properties are not supported");
+                               }
+                               Value value = p.getValue();
+                               return (T) get(value);
+                       } catch (ClassCastException e) {
+                               throw new IllegalArgumentException(
+                                               "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
                }
+//             if (String.class.isAssignableFrom(clss)) {
+//                     return (T) get(node, property);
+//             } else if (Long.class.isAssignableFrom(clss)) {
+//                     return (T) get(node, property);
+//             } else if (Boolean.class.isAssignableFrom(clss)) {
+//                     return (T) get(node, property);
+//             } else {
+//                     throw new IllegalArgumentException("Unsupported format " + clss);
+//             }
        }
 
        /**
index d9cae87d811258d5a13e43eea8492f3792377ce4..d5943f556d6fba9db0dd63d4c4cfceef89e4888e 160000 (submodule)
@@ -1 +1 @@
-Subproject commit d9cae87d811258d5a13e43eea8492f3792377ce4
+Subproject commit d5943f556d6fba9db0dd63d4c4cfceef89e4888e
index 29a2ca76a98f0ba967738211ca53acc44728da1b..50e2eea46a8ad0251d241854662e623cd7cc6a05 100644 (file)
@@ -1,6 +1,6 @@
 major=2
 minor=3
-micro=15
+micro=18
 qualifier=
 
 Bundle-Copyright= \
@@ -9,4 +9,4 @@ Copyright 2012-2023 Argeo GmbH
 
 SPDX-License-Identifier= \
 GPL-2.0-or-later \
-OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-JCR-permissions
+OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-Apache-and-JCR-permissions
diff --git a/swt/org.argeo.cms.jcr.ui/src/org/argeo/cms/ui/CmsUiConstants.java b/swt/org.argeo.cms.jcr.ui/src/org/argeo/cms/ui/CmsUiConstants.java
deleted file mode 100644 (file)
index fd1dda5..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.ui;
-
-/** Commons constants */
-@Deprecated
-public interface CmsUiConstants {
-       // DATAKEYS
-//     public final static String STYLE = EclipseUiConstants.CSS_CLASS;
-//     public final static String MARKUP = EclipseUiConstants.MARKUP_SUPPORT;
-       @Deprecated
-       /* RWT.CUSTOM_ITEM_HEIGHT */
-       public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight";
-
-       // EVENT DETAILS
-       @Deprecated
-       /* RWT.HYPERLINK */
-       public final static int HYPERLINK = 1 << 26;
-
-       // STANDARD RESOURCES
-       public final static String LOADING_IMAGE = "icons/loading.gif";
-
-       // MISCEALLENEOUS
-       String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm";
-}
index 3522f1b423b3f3e2fc09422aa36e556c95188ad5..94c5808e4ca9758a13f1f7760c903354a2b64cb9 100644 (file)
@@ -1,7 +1,5 @@
 package org.argeo.cms.ui.util;
 
-import java.io.IOException;
-import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 
@@ -12,16 +10,14 @@ import javax.servlet.http.HttpServletRequest;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.ux.Cms2DSize;
 import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.acr.ContentUtils;
 import org.argeo.cms.jcr.CmsJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiConstants;
 import org.argeo.cms.ux.AbstractImageManager;
 import org.argeo.cms.ux.CmsUxUtils;
 import org.argeo.jcr.JcrUtils;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.rap.rwt.service.ResourceManager;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.layout.RowData;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
@@ -82,24 +78,22 @@ public class CmsUiUtils {
 
        /** Clean reserved URL characters for use in HTTP links. */
        public static String getDataPathForUrl(Node node) {
-               return CmsSwtUtils.cleanPathForUrl(getDataPath(node));
+               return ContentUtils.cleanPathForUrl(getDataPath(node));
        }
 
        /** @deprecated Use rowData16px() instead. GridData should not be reused. */
        @Deprecated
        public static RowData ROW_DATA_16px = new RowData(16, 16);
 
-       
-
        /*
         * FORM LAYOUT
         */
 
-       
+       public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight";
 
        @Deprecated
        public static void setItemHeight(Table table, int height) {
-               table.setData(CmsUiConstants.ITEM_HEIGHT, height);
+               table.setData(ITEM_HEIGHT, height);
        }
 
        //
index e3a5cb473d588fa47254ddb752773f04fda823d1..d00c52468b24fd0f52ee98618e6fa2f3c1c99bc4 100644 (file)
@@ -3,7 +3,6 @@ package org.argeo.cms.ui.widgets;
 import javax.jcr.Item;
 
 import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiConstants;
 import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.FocusListener;
@@ -12,7 +11,7 @@ import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 
 /** Editable text part displaying styled text. */
-public abstract class StyledControl extends JcrComposite implements CmsUiConstants {
+public abstract class StyledControl extends JcrComposite {
        private static final long serialVersionUID = -6372283442330912755L;
        private Control control;