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