View Javadoc
1   /**
2    * This file Copyright (c) 2011-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.jcr.util;
35  
36  import info.magnolia.cms.util.DateUtil;
37  import info.magnolia.context.MgnlContext;
38  
39  import java.io.ByteArrayInputStream;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.math.BigDecimal;
43  import java.net.URI;
44  import java.util.ArrayList;
45  import java.util.Calendar;
46  import java.util.Collection;
47  import java.util.Date;
48  import java.util.GregorianCalendar;
49  import java.util.LinkedList;
50  import java.util.List;
51  import java.util.TimeZone;
52  
53  import javax.jcr.Binary;
54  import javax.jcr.Node;
55  import javax.jcr.Property;
56  import javax.jcr.PropertyType;
57  import javax.jcr.RepositoryException;
58  import javax.jcr.Value;
59  import javax.jcr.ValueFactory;
60  
61  import org.apache.commons.lang3.BooleanUtils;
62  import org.apache.commons.lang3.StringUtils;
63  import org.apache.commons.lang3.time.FastDateFormat;
64  import org.apache.jackrabbit.value.BinaryImpl;
65  import org.apache.jackrabbit.value.ValueFactoryImpl;
66  import org.slf4j.Logger;
67  import org.slf4j.LoggerFactory;
68  
69  /**
70   * Property-related utility methods.
71   */
72  public class PropertyUtil {
73  
74      private static final Logger log = LoggerFactory.getLogger(PropertyUtil.class);
75  
76      public static Property renameProperty(Property property, String newName) throws RepositoryException {
77          // Do nothing if the property already has this name, otherwise we would remove the property
78          if (property.getName().equals(newName)) {
79              return property;
80          }
81          Node node = property.getParent();
82          Property newProperty = node.setProperty(newName, property.getValue());
83          property.remove();
84          return newProperty;
85      }
86  
87      /**
88       * Allows setting a Node's property from an object.
89       */
90      public static void setProperty(Node node, String propertyName, Object propertyValue) throws RepositoryException {
91          if (node == null) {
92              throw new IllegalArgumentException("Cannot set a property " + (StringUtils.isNotEmpty(propertyName) ? ("[" + propertyName + "]") : "") + " on a null-node!");
93          }
94          if (propertyName == null) {
95              throw new IllegalArgumentException("Cannot set a property without a provided name");
96          }
97  
98          // JCR does not support changing the cardinality of a property. To support this we remove the
99          // existing property if a cardinality change is detected.
100         Property property = getPropertyOrNull(node, propertyName);
101         if (property != null && property.isMultiple() != propertyValue instanceof Collection) {
102             property.remove();
103         }
104 
105         if (propertyValue == null) {
106             node.setProperty(propertyName, (Value) null);
107         } else if (propertyValue instanceof Value) {
108             node.setProperty(propertyName, (Value) propertyValue);
109         } else if (propertyValue instanceof Node) {
110             node.setProperty(propertyName, (Node) propertyValue);
111         } else if (propertyValue instanceof Binary) {
112             node.setProperty(propertyName, (Binary) propertyValue);
113         } else if (propertyValue instanceof Calendar) {
114             node.setProperty(propertyName, (Calendar) propertyValue);
115         } else if (propertyValue instanceof Date) {
116             Calendar cal = Calendar.getInstance();
117             cal.setTime((Date) propertyValue);
118             node.setProperty(propertyName, cal);
119         } else if (propertyValue instanceof BigDecimal) {
120             node.setProperty(propertyName, (BigDecimal) propertyValue);
121         } else if (propertyValue instanceof String) {
122             node.setProperty(propertyName, (String) propertyValue);
123         } else if (propertyValue instanceof Integer) {
124             node.setProperty(propertyName, ((Integer) propertyValue));
125         } else if (propertyValue instanceof Long) {
126             node.setProperty(propertyName, ((Long) propertyValue));
127         } else if (propertyValue instanceof Double) {
128             node.setProperty(propertyName, (Double) propertyValue);
129         } else if (propertyValue instanceof Boolean) {
130             node.setProperty(propertyName, (Boolean) propertyValue);
131         } else if (propertyValue instanceof InputStream) {
132             node.setProperty(propertyName, createBinaryFromInputStream((InputStream) propertyValue));
133         } else if (propertyValue instanceof byte[]) {
134             final Binary binary = node.getSession().getValueFactory().createBinary(new ByteArrayInputStream((byte[]) propertyValue));
135             Value value = node.getSession().getValueFactory().createValue(binary);
136             node.setProperty(propertyName, value);
137         } else if (propertyValue instanceof Collection) {
138             ArrayList<Value> values = new ArrayList<>();
139             for (Object value : (Collection) propertyValue) {
140                 values.add(value instanceof Value ? (Value) value : createValue(value, node.getSession().getValueFactory()));
141             }
142             node.setProperty(propertyName, values.toArray(new Value[values.size()]));
143         } else {
144             // TODO dlipp: verify if this is desired default-behavior: NodeDataUtil#setValue sets propertyValue.toString() as default!
145             throw new IllegalArgumentException("Cannot set property to a value of type " + propertyValue.getClass());
146         }
147     }
148 
149     /**
150      * Transforms a string to a jcr value object.
151      */
152     public static Value createValue(String valueStr, int type, ValueFactory valueFactory) {
153         Value value = null;
154         if (type == PropertyType.STRING) {
155             value = valueFactory.createValue(valueStr);
156         } else if (type == PropertyType.BOOLEAN) {
157             value = valueFactory.createValue(BooleanUtils.toBoolean(valueStr));
158         } else if (type == PropertyType.DOUBLE) {
159             try {
160                 value = valueFactory.createValue(Double.parseDouble(valueStr));
161             } catch (NumberFormatException e) {
162                 value = valueFactory.createValue(0d);
163             }
164         } else if (type == PropertyType.LONG) {
165             try {
166                 value = valueFactory.createValue(Long.parseLong(valueStr));
167             } catch (NumberFormatException e) {
168                 value = valueFactory.createValue(0L);
169             }
170         } else if (type == PropertyType.DATE) {
171             try {
172                 Calendar date = new GregorianCalendar();
173                 try {
174                     String newDateAndTime = valueStr;
175                     String[] dateAndTimeTokens = newDateAndTime.split("T");
176                     String newDate = dateAndTimeTokens[0];
177                     String[] dateTokens = newDate.split("-");
178                     int hour = 0;
179                     int minute = 0;
180                     int second = 0;
181                     int year = Integer.parseInt(dateTokens[0]);
182                     int month = Integer.parseInt(dateTokens[1]) - 1;
183                     int day = Integer.parseInt(dateTokens[2]);
184                     if (dateAndTimeTokens.length > 1) {
185                         String newTime = dateAndTimeTokens[1];
186                         String[] timeTokens = newTime.split(":");
187                         hour = Integer.parseInt(timeTokens[0]);
188                         minute = Integer.parseInt(timeTokens[1]);
189                         second = Integer.parseInt(timeTokens[2]);
190                     }
191                     date.set(year, month, day, hour, minute, second);
192                     // this is used in the searching
193                     date.set(Calendar.MILLISECOND, 0);
194                     date.setTimeZone(TimeZone.getTimeZone("GMT"));
195                 }
196                 // todo time zone??
197                 catch (Exception e) {
198                     // ignore, it sets the current date / time
199                 }
200                 value = valueFactory.createValue(date);
201             } catch (Exception e) {
202                 log.debug("Exception caught: {}", e.getMessage(), e);
203             }
204         }
205 
206         return value;
207 
208     }
209 
210     /**
211      * @return JCR-PropertyType corresponding to provided Object.
212      */
213     public static int getJCRPropertyType(Object obj) {
214         if (obj instanceof String) {
215             return PropertyType.STRING;
216         }
217         if (obj instanceof Double) {
218             return PropertyType.DOUBLE;
219         }
220         if (obj instanceof Float) {
221             return PropertyType.DOUBLE;
222         }
223         if (obj instanceof Long) {
224             return PropertyType.LONG;
225         }
226         if (obj instanceof Integer) {
227             return PropertyType.LONG;
228         }
229         if (obj instanceof Boolean) {
230             return PropertyType.BOOLEAN;
231         }
232         if (obj instanceof Calendar) {
233             return PropertyType.DATE;
234         }
235         if (obj instanceof Date) {
236             return PropertyType.DATE;
237         }
238         if (obj instanceof Binary) {
239             return PropertyType.BINARY;
240         }
241         if (obj instanceof InputStream) {
242             return PropertyType.BINARY;
243         }
244         if (obj instanceof Node) {
245             return PropertyType.REFERENCE;
246         }
247         if (obj instanceof URI) {
248             return PropertyType.URI;
249         }
250         return PropertyType.UNDEFINED;
251     }
252 
253     /**
254      * Updates existing property or creates a new one if it doesn't exist already.
255      */
256     public static void updateOrCreate(Node node, String string, GregorianCalendar gregorianCalendar) throws RepositoryException {
257         if (node.hasProperty(string)) {
258             node.getProperty(string).setValue(gregorianCalendar);
259         } else {
260             node.setProperty(string, gregorianCalendar);
261         }
262     }
263 
264     public static String getDateFormat() {
265         try {
266             return FastDateFormat.getDateInstance(
267                     FastDateFormat.SHORT,
268                     MgnlContext.getLocale()).getPattern();
269         } catch (IllegalStateException e) {
270             // this happens if the context is not (yet) set
271             return DateUtil.YYYY_MM_DD;
272         }
273     }
274 
275     public static List<String> getValuesStringList(Value[] values) {
276         ArrayList<String> list = new ArrayList<String>();
277         for (Value value : values) {
278             list.add(getValueString(value));
279         }
280         return list;
281     }
282 
283     /**
284      * Returns value of the property converted to string no matter what it's type actually is. In case of dates, value if formatted according to format returned by {@link #getDateFormat()}. Binary and reference values are converted to empty string. In case of error during conversion, null will be returned instead. Works only for single value properties.
285      */
286     public static String getValueString(Property property) {
287         try {
288             return getValueString(property.getValue());
289         } catch (RepositoryException e) {
290             log.debug("RepositoryException caught: {}", e.getMessage(), e);
291             return null;
292         }
293     }
294 
295     /**
296      * Returns value converted to string no matter what it's type actually is. In case of dates, value if formatted according to format returned by {@link #getDateFormat()}. Binary and reference values are converted to empty string. In case of error during conversion, null will be returned instead.
297      */
298     public static String getValueString(Value value) {
299         try {
300             switch (value.getType()) {
301             case (PropertyType.STRING):
302                 return value.getString();
303             case (PropertyType.DOUBLE):
304                 return Double.toString(value.getDouble());
305             case (PropertyType.LONG):
306                 return Long.toString(value.getLong());
307             case (PropertyType.BOOLEAN):
308                 return Boolean.toString(value.getBoolean());
309             case (PropertyType.DATE):
310                 Date valueDate = value.getDate().getTime();
311                 return DateUtil.format(valueDate, PropertyUtil.getDateFormat());
312             case (PropertyType.BINARY):
313                 // for lack of better solution, fall through to the default - empty string
314             default:
315                 return StringUtils.EMPTY;
316             }
317         } catch (RepositoryException e) {
318             log.debug("RepositoryException caught: {}", e.getMessage(), e);
319         }
320         return null;
321 
322     }
323 
324     public static Value createValue(Object obj, ValueFactory valueFactory) throws RepositoryException {
325         switch (PropertyUtil.getJCRPropertyType(obj)) {
326         case PropertyType.STRING:
327             return valueFactory.createValue((String) obj);
328         case PropertyType.BOOLEAN:
329             return valueFactory.createValue((Boolean) obj);
330         case PropertyType.DATE:
331             if (obj instanceof Calendar) {
332                 return valueFactory.createValue((Calendar) obj);
333             } else {
334                 Calendar cal = Calendar.getInstance();
335                 cal.setTime((Date) obj);
336                 return valueFactory.createValue(cal);
337             }
338         case PropertyType.LONG:
339             return obj instanceof Long ? valueFactory.createValue(((Long) obj).longValue()) : valueFactory.createValue(((Integer) obj).longValue());
340         case PropertyType.DOUBLE:
341             return obj instanceof Double ? valueFactory.createValue((Double) obj) : valueFactory.createValue(((Float) obj).doubleValue());
342         case PropertyType.BINARY:
343             return valueFactory.createValue(createBinaryFromInputStream((InputStream) obj));
344         case PropertyType.REFERENCE:
345             return valueFactory.createValue(((Node) obj));
346         default:
347             return (obj != null ? valueFactory.createValue(obj.toString()) : valueFactory.createValue(StringUtils.EMPTY));
348         }
349     }
350 
351     /**
352      * Return the Calendar representing the node property value.
353      * If the Node did not contain such a Property,
354      * then return <b>null</b>.
355      */
356     public static Calendar getDate(Node node, String name) {
357         return getDate(node, name, null);
358     }
359 
360     /**
361      * Return the Calendar representing the node property value.
362      * If the Node did not contain such a Property,
363      * then return the default value.
364      */
365     public static Calendar getDate(Node node, String name, Calendar defaultValue) {
366         if (node != null) {
367             try {
368                 if (node.hasProperty(name)) {
369                     return node.getProperty(name).getDate();
370                 }
371             } catch (RepositoryException e) {
372                 log.error("can't read value '{}' of the Node '{}' will return default value", name, node.toString(), e);
373             }
374         }
375         return defaultValue;
376     }
377 
378     /**
379      * Return the String representing the node property value.
380      * If the Node did not contain such a Property or if the Node is null,
381      * then return <b>null</b>.
382      */
383     public static String getString(Node node, String name) {
384         return getString(node, name, null);
385     }
386 
387     /**
388      * Return the String representing the node property value.
389      * If the Node did not contain such a Property or if the Node is null,
390      * then return the default value.
391      */
392     public static String getString(Node node, String name, String defaultValue) {
393         if (node != null) {
394             try {
395                 if (node.hasProperty(name)) {
396                     return node.getProperty(name).getString();
397                 }
398             } catch (RepositoryException e) {
399                 log.error("can't read value '{}' of the Node '{}' will return default value", name, node.toString(), e);
400             }
401         }
402         return defaultValue;
403     }
404 
405     public static Long getLong(Node node, String name) {
406         return getLong(node, name, null);
407     }
408 
409     public static Long getLong(Node node, String name, Long defaultValue) {
410         if (node != null) {
411             try {
412                 if (node.hasProperty(name)) {
413                     return node.getProperty(name).getLong();
414                 }
415             } catch (RepositoryException e) {
416                 log.error("can't read value '{}' of the Node '{}' will return default value", name, node.toString(), e);
417             }
418         }
419         return defaultValue;
420     }
421 
422     /**
423      * Return the boolean representing the node property value.
424      * If the Node did not contain such a Property,
425      * then return the default value.
426      */
427     public static boolean getBoolean(Node node, String name, boolean defaultValue) {
428         if (node != null) {
429             try {
430                 if (node.hasProperty(name)) {
431                     return node.getProperty(name).getBoolean();
432                 }
433             } catch (RepositoryException e) {
434                 log.error("can't read value '{}' of the Node '{}' will return default value", name, node.toString(), e);
435             }
436         }
437         return defaultValue;
438     }
439 
440 
441     /**
442      * Return the Property relative to the Node or null if it's not existing or in case of any RepositoryException.
443      */
444     public static Property getPropertyOrNull(Node node, String relativePath) {
445         try {
446             return node.hasProperty(relativePath) ? node.getProperty(relativePath) : null;
447         } catch (RepositoryException e) {
448             log.debug("Could not retrieve property {}", relativePath, e);
449         }
450         return null;
451     }
452 
453     /**
454      * @deprecated since 4.5 - use getPropertyOrNull instead
455      */
456     @Deprecated
457     public static Property getProperty(Node node, String relativePath) {
458         return getPropertyOrNull(node, relativePath);
459     }
460 
461     /**
462      * Return the Value Object from a property.
463      * Return null in case of exception.
464      * The returned Object could be a basic {@link PropertyType} type or in case
465      * of multivalue, a LinkedList of {@link PropertyType} type objects.
466      */
467     public static Object getPropertyValueObject(Node node, String relativePath) {
468         final Property property = getPropertyOrNull(node, relativePath);
469         if (property != null) {
470             try {
471                 //Handle Multivalue fields
472                 if (property.isMultiple()) {
473                     Value[] values = property.getValues();
474                     List<Object> res = new LinkedList<Object>();
475                     for (Value value : values) {
476                         res.add(getValueObject(value));
477                     }
478                     return res;
479                 } else {
480                     return getValueObject(property.getValue());
481                 }
482 
483             } catch (Exception e) {
484                 log.warn("Exception during casting the property value", e);
485             }
486         }
487         return null;
488     }
489 
490     /**
491      * Return the Value Object from a {@link Value}.
492      * Return null in case of exception.
493      */
494     public static Object getValueObject(Value value) {
495         try {
496             switch (value.getType()) {
497                 case (PropertyType.DECIMAL):
498                     return value.getDecimal();
499                 case (PropertyType.STRING):
500                     return value.getString();
501                 case (PropertyType.DOUBLE):
502                     return Double.valueOf(value.getDouble());
503                 case (PropertyType.LONG):
504                     return Long.valueOf(value.getLong());
505                 case (PropertyType.BOOLEAN):
506                     return Boolean.valueOf(value.getBoolean());
507                 case (PropertyType.DATE):
508                     return value.getDate().getTime();
509                 case (PropertyType.URI):
510                     return new URI(value.getString());
511                 case (PropertyType.BINARY):
512                     return ValueFactoryImpl.getInstance().createBinary(value.getBinary().getStream());
513                 default:
514                     return value.getString();
515             }
516         } catch (Exception e) {
517             log.warn("Exception during casting the property value", e);
518         }
519         return null;
520     }
521 
522     /**
523      * Create a {@link Binary} object based of an InputStream. <br>
524      *
525      * @throws RepositoryException in case of exception during the creation of {@link Binary} .
526      */
527     private static Binary createBinaryFromInputStream(InputStream in) throws RepositoryException {
528         try {
529             return new BinaryImpl(in);
530         } catch (IOException ioe) {
531             log.warn("Could not create a Binary Object from the property input Stream.");
532             throw new RepositoryException(ioe);
533         }
534     }
535 
536 }