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