Fix ' when using JCR query based on MessageFormat.
[lgpl/argeo-commons.git] / org.argeo.jcr / src / org / argeo / jcr / Jcr.java
index 4b8bfc3eae5761bcfd3d04f8cd188706ba99c242..72e325d35a40c40ad22a712ae7561fd33ae6ed87 100644 (file)
@@ -1,5 +1,8 @@
 package org.argeo.jcr;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.text.MessageFormat;
 import java.time.Instant;
@@ -32,6 +35,8 @@ import javax.jcr.version.VersionHistory;
 import javax.jcr.version.VersionIterator;
 import javax.jcr.version.VersionManager;
 
+import org.apache.commons.io.IOUtils;
+
 /**
  * Utility class whose purpose is to make using JCR less verbose by
  * systematically using unchecked exceptions and returning <code>null</code>
@@ -132,6 +137,14 @@ public class Jcr {
                }
        }
 
+       /**
+        * @see Node#getParent()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getParentPath(Node node) {
+               return getPath(getParent(node));
+       }
+
        /**
         * Whether this node is the root node.
         * 
@@ -190,6 +203,48 @@ public class Jcr {
                }
        }
 
+       /**
+        * Returns the node name with its current index (useful for re-ordering).
+        * 
+        * @see Node#getName()
+        * @see Node#getIndex()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getIndexedName(Node node) {
+               try {
+                       return node.getName() + "[" + node.getIndex() + "]";
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get name of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getProperty(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Property getProperty(Node node, String property) {
+               try {
+                       if (node.hasProperty(property))
+                               return node.getProperty(property);
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get property " + property + " of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getIndex()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static int getIndex(Node node) {
+               try {
+                       return node.getIndex();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get index of " + node, e);
+               }
+       }
+
        /**
         * If node has mixin {@link NodeType#MIX_TITLE}, return
         * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
@@ -381,11 +436,19 @@ public class Jcr {
        public static void set(Node node, String property, Object value) {
                try {
                        if (!node.hasProperty(property)) {
-                               if (value != null)
-                                       node.setProperty(property, value.toString());
+                               if (value != null) {
+                                       if (value instanceof List) {// multiple
+                                               List<?> lst = (List<?>) value;
+                                               String[] values = new String[lst.size()];
+                                               for (int i = 0; i < lst.size(); i++) {
+                                                       values[i] = lst.get(i).toString();
+                                               }
+                                               node.setProperty(property, values);
+                                       } else {
+                                               node.setProperty(property, value.toString());
+                                       }
+                               }
                                return;
-                               // throw new IllegalArgumentException("No property " + property + " in " +
-                               // node);
                        }
                        Property prop = node.getProperty(property);
                        if (value == null) {
@@ -393,6 +456,27 @@ public class Jcr {
                                return;
                        }
 
+                       // multiple
+                       if (value instanceof List) {
+                               List<?> lst = (List<?>) value;
+                               String[] values = new String[lst.size()];
+                               // TODO better cast?
+                               for (int i = 0; i < lst.size(); i++) {
+                                       values[i] = lst.get(i).toString();
+                               }
+                               if (!prop.isMultiple())
+                                       prop.remove();
+                               node.setProperty(property, values);
+                               return;
+                       }
+
+                       // single
+                       if (prop.isMultiple()) {
+                               prop.remove();
+                               node.setProperty(property, value.toString());
+                               return;
+                       }
+
                        if (value instanceof String)
                                prop.setValue((String) value);
                        else if (value instanceof Long)
@@ -498,20 +582,11 @@ public class Jcr {
                        if (node.hasProperty(property)) {
                                Property p = node.getProperty(property);
                                try {
-                                       switch (p.getType()) {
-                                       case PropertyType.STRING:
-                                               return (T) node.getProperty(property).getString();
-                                       case PropertyType.DOUBLE:
-                                               return (T) (Double) node.getProperty(property).getDouble();
-                                       case PropertyType.LONG:
-                                               return (T) (Long) node.getProperty(property).getLong();
-                                       case PropertyType.BOOLEAN:
-                                               return (T) (Boolean) node.getProperty(property).getBoolean();
-                                       case PropertyType.DATE:
-                                               return (T) node.getProperty(property).getDate();
-                                       default:
-                                               return (T) node.getProperty(property).getString();
+                                       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);
@@ -524,6 +599,86 @@ public class Jcr {
                }
        }
 
+       /**
+        * Get a multiple property as a list, doing a best effort to cast it as the
+        * target list.
+        * 
+        * @return the value of {@link Node#getProperty(String)}.
+        * @throws IllegalArgumentException if the value could not be cast
+        * @throws JcrException             in case of unexpected
+        *                                  {@link RepositoryException}
+        */
+       public static <T> List<T> getMultiple(Node node, String property) {
+               try {
+                       if (node.hasProperty(property)) {
+                               Property p = node.getProperty(property);
+                               return getMultiple(p);
+                       } else {
+                               return null;
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
+               }
+       }
+
+       /**
+        * Get a multiple property as a list, doing a best effort to cast it as the
+        * target list.
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> List<T> getMultiple(Property p) {
+               try {
+                       List<T> res = new ArrayList<>();
+                       if (!p.isMultiple()) {
+                               res.add((T) get(p.getValue()));
+                               return res;
+                       }
+                       Value[] values = p.getValues();
+                       for (Value value : values) {
+                               res.add((T) get(value));
+                       }
+                       return res;
+               } catch (ClassCastException | RepositoryException e) {
+                       throw new IllegalArgumentException("Cannot get property " + p, e);
+               }
+       }
+
+       /** Cast a {@link Value} to a standard Java object. */
+       public static Object get(Value value) {
+               Binary binary = null;
+               try {
+                       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:
+                               return value.getDate();
+                       case PropertyType.BINARY:
+                               binary = value.getBinary();
+                               byte[] arr = null;
+                               try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+                                       IOUtils.copy(in, out);
+                                       arr = out.toByteArray();
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot read binary from " + value, e);
+                               }
+                               return arr;
+                       default:
+                               return value.getString();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot cast value from " + value, e);
+               } finally {
+                       if (binary != null)
+                               binary.dispose();
+               }
+       }
+
        /**
         * Retrieves the {@link Session} related to this node.
         * 
@@ -747,6 +902,8 @@ public class Jcr {
        // QUERY
        /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
        public static Query createQuery(QueryManager qm, String sql, Object... args) {
+               // fix single quotes
+               sql = sql.replaceAll("'", "''");
                String query = MessageFormat.format(sql, args);
                try {
                        return qm.createQuery(query, Query.JCR_SQL2);