View Javadoc

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