View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.jcr;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.math.BigDecimal;
29  import java.net.URI;
30  import java.util.Calendar;
31  import javax.jcr.Node;
32  import javax.jcr.PropertyType;
33  import javax.jcr.RepositoryException;
34  import javax.jcr.Value;
35  import javax.jcr.ValueFormatException;
36  import net.jcip.annotations.NotThreadSafe;
37  import org.modeshape.common.SystemFailureException;
38  import org.modeshape.common.util.IoUtil;
39  import org.modeshape.graph.property.Binary;
40  import org.modeshape.graph.property.BinaryFactory;
41  import org.modeshape.graph.property.DateTime;
42  import org.modeshape.graph.property.DateTimeFactory;
43  import org.modeshape.graph.property.Name;
44  import org.modeshape.graph.property.NameFactory;
45  import org.modeshape.graph.property.Path;
46  import org.modeshape.graph.property.PathFactory;
47  import org.modeshape.graph.property.ValueFactories;
48  
49  /**
50   * ModeShape implementation of a {@link Value JCR Value}.
51   */
52  @NotThreadSafe
53  final class JcrValue implements Value, org.modeshape.jcr.api.Value {
54  
55      static final JcrValue[] EMPTY_ARRAY = new JcrValue[] {};
56  
57      private final SessionCache sessionCache;
58      private final ValueFactories valueFactories;
59      private final int type;
60      private final Object value;
61      private InputStream asStream = null;
62  
63      JcrValue( ValueFactories valueFactories,
64                SessionCache sessionCache,
65                int type,
66                Object value ) {
67          assert valueFactories != null;
68          assert type == PropertyType.BINARY || type == PropertyType.BOOLEAN || type == PropertyType.DATE
69                 || type == PropertyType.DECIMAL || type == PropertyType.DOUBLE || type == PropertyType.LONG
70                 || type == PropertyType.NAME || type == PropertyType.PATH || type == PropertyType.REFERENCE
71                 || type == PropertyType.WEAKREFERENCE || type == PropertyType.STRING || type == PropertyType.URI : "Unxpected PropertyType: "
72                                                                                                                    + PropertyType.nameFromValue(type)
73                                                                                                                    + " for value "
74                                                                                                                    + (value == null ? "null" : ("\""
75                                                                                                                                                 + value + "\""));
76  
77          // Leaving this assertion out for now so that values can be created in node type sources, which are created outside
78          // the context of any particular session.
79          // assert sessionCache != null;
80          assert value != null;
81  
82          this.valueFactories = valueFactories;
83          this.sessionCache = sessionCache;
84          this.type = type;
85          this.value = value instanceof JcrBinary ? ((JcrBinary)value).binary() : value;
86      }
87  
88      JcrValue( ValueFactories valueFactories,
89                SessionCache sessionCache,
90                Value value ) throws RepositoryException {
91          assert value != null;
92  
93          this.valueFactories = valueFactories;
94          this.sessionCache = sessionCache;
95          this.type = value.getType();
96          this.value = valueToType(this.type, value);
97      }
98  
99      private ValueFormatException createValueFormatException( Class<?> type ) {
100         return new ValueFormatException(JcrI18n.cannotConvertValue.text(value.getClass().getSimpleName(), type.getSimpleName()));
101     }
102 
103     private ValueFormatException createValueFormatException( org.modeshape.graph.property.ValueFormatException vfe ) {
104         return new ValueFormatException(vfe);
105     }
106 
107     /**
108      * Returns a direct reference to the internal value object wrapped by this {@link JcrValue}. Useful to avoid the expense of
109      * {@link #asType(int)} if the caller already knows the type of the value.
110      * 
111      * @return a reference to the {@link #value} field.
112      */
113     final Object value() {
114         return value;
115     }
116 
117     /**
118      * Returns the session cache for the session that created this value.
119      * 
120      * @return the session cache for the session that created this value.
121      */
122     final SessionCache sessionCache() {
123         return sessionCache;
124     }
125 
126     /**
127      * {@inheritDoc}
128      * 
129      * @see javax.jcr.Value#getBoolean()
130      */
131     public boolean getBoolean() throws ValueFormatException {
132         try {
133             boolean convertedValue = valueFactories.getBooleanFactory().create(value);
134             return convertedValue;
135         } catch (RuntimeException error) {
136             throw createValueFormatException(boolean.class);
137         }
138     }
139 
140     /**
141      * {@inheritDoc}
142      * 
143      * @see javax.jcr.Value#getDate()
144      */
145     public Calendar getDate() throws ValueFormatException {
146         try {
147             Calendar convertedValue = valueFactories.getDateFactory().create(value).toCalendar();
148             return convertedValue;
149         } catch (RuntimeException error) {
150             throw createValueFormatException(Calendar.class);
151         }
152     }
153 
154     /**
155      * {@inheritDoc}
156      * 
157      * @see javax.jcr.Value#getDecimal()
158      */
159     @Override
160     public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
161         try {
162             BigDecimal convertedValue = valueFactories.getDecimalFactory().create(value);
163             return convertedValue;
164         } catch (RuntimeException error) {
165             throw createValueFormatException(double.class);
166         }
167     }
168 
169     /**
170      * {@inheritDoc}
171      * 
172      * @see javax.jcr.Value#getDouble()
173      */
174     public double getDouble() throws ValueFormatException {
175         try {
176             double convertedValue = valueFactories.getDoubleFactory().create(value);
177             return convertedValue;
178         } catch (RuntimeException error) {
179             throw createValueFormatException(double.class);
180         }
181     }
182 
183     long getLength() throws RepositoryException {
184         if (type == PropertyType.BINARY) {
185             return valueFactories.getBinaryFactory().create(value).getSize();
186         }
187         return getString().length();
188     }
189 
190     /**
191      * {@inheritDoc}
192      * 
193      * @see javax.jcr.Value#getLong()
194      */
195     public long getLong() throws ValueFormatException {
196         try {
197             long convertedValue = valueFactories.getLongFactory().create(value);
198             return convertedValue;
199         } catch (RuntimeException error) {
200             throw createValueFormatException(long.class);
201         }
202     }
203 
204     /**
205      * {@inheritDoc}
206      * 
207      * @see javax.jcr.Value#getStream()
208      */
209     public InputStream getStream() throws ValueFormatException {
210         try {
211             if (asStream == null) {
212                 Binary binary = valueFactories.getBinaryFactory().create(value);
213                 asStream = new SelfClosingInputStream(binary);
214             }
215             return asStream;
216         } catch (RuntimeException error) {
217             throw createValueFormatException(InputStream.class);
218         }
219     }
220 
221     /**
222      * {@inheritDoc}
223      * 
224      * @see org.modeshape.jcr.api.Value#getBinary()
225      */
226     public javax.jcr.Binary getBinary() throws RepositoryException {
227         try {
228             Binary binary = valueFactories.getBinaryFactory().create(value);
229             return new JcrBinary(binary);
230         } catch (RuntimeException error) {
231             throw createValueFormatException(InputStream.class);
232         }
233     }
234 
235     /**
236      * {@inheritDoc}
237      * 
238      * @see javax.jcr.Value#getString()
239      */
240     public String getString() throws ValueFormatException {
241         try {
242             String convertedValue = valueFactories.getStringFactory().create(value);
243             return convertedValue;
244         } catch (RuntimeException error) {
245             throw createValueFormatException(String.class);
246         }
247     }
248 
249     /**
250      * {@inheritDoc}
251      * 
252      * @see javax.jcr.Value#getType()
253      */
254     public int getType() {
255         return type;
256     }
257 
258     /**
259      * {@inheritDoc}
260      * 
261      * @see java.lang.Object#hashCode()
262      */
263     @Override
264     public int hashCode() {
265         // Use the value's hash code
266         return value.hashCode();
267     }
268 
269     /**
270      * {@inheritDoc}
271      * 
272      * @see java.lang.Object#equals(java.lang.Object)
273      */
274     @Override
275     public boolean equals( Object obj ) {
276         if (obj == this) return true;
277         if (obj instanceof JcrValue) {
278             JcrValue that = (JcrValue)obj;
279             if (this.type != that.type) return false;
280             try {
281                 switch (this.type) {
282                     case PropertyType.STRING:
283                         return this.getString().equals(that.getString());
284                     case PropertyType.BINARY:
285                         BinaryFactory binaryFactory = valueFactories.getBinaryFactory();
286                         Binary thisValue = binaryFactory.create(this.value);
287                         Binary thatValue = binaryFactory.create(that.value);
288                         return thisValue.equals(thatValue);
289                     case PropertyType.BOOLEAN:
290                         return this.getBoolean() == that.getBoolean();
291                     case PropertyType.DOUBLE:
292                         return this.getDouble() == that.getDouble();
293                     case PropertyType.LONG:
294                         return this.getLong() == that.getLong();
295                     case PropertyType.DECIMAL:
296                         return getDecimal().equals(that.getDecimal());
297                     case PropertyType.DATE:
298                         DateTimeFactory dateFactory = valueFactories.getDateFactory();
299                         DateTime thisDateValue = dateFactory.create(this.value);
300                         DateTime thatDateValue = dateFactory.create(that.value);
301                         return thisDateValue.equals(thatDateValue);
302                     case PropertyType.PATH:
303                         PathFactory pathFactory = valueFactories.getPathFactory();
304                         Path thisPathValue = pathFactory.create(this.value);
305                         Path thatPathValue = pathFactory.create(that.value);
306                         return thisPathValue.equals(thatPathValue);
307                     case PropertyType.NAME:
308                         NameFactory nameFactory = valueFactories.getNameFactory();
309                         Name thisNameValue = nameFactory.create(this.value);
310                         Name thatNameValue = nameFactory.create(that.value);
311                         return thisNameValue.equals(thatNameValue);
312                     case PropertyType.REFERENCE:
313                     case PropertyType.WEAKREFERENCE:
314                         return this.getString().equals(that.getString());
315                     default:
316                         throw new SystemFailureException(JcrI18n.invalidPropertyType.text(this.type));
317                 }
318             } catch (RepositoryException e) {
319                 return false;
320             }
321             // will not get here
322         }
323         if (obj instanceof Value) {
324             Value that = (Value)obj;
325             if (this.type != that.getType()) return false;
326             try {
327                 switch (this.type) {
328                     case PropertyType.STRING:
329                         return this.getString().equals(that.getString());
330                     case PropertyType.BINARY:
331                         return IoUtil.isSame(this.getStream(), that.getBinary().getStream());
332                     case PropertyType.BOOLEAN:
333                         return this.getBoolean() == that.getBoolean();
334                     case PropertyType.DOUBLE:
335                         return this.getDouble() == that.getDouble();
336                     case PropertyType.LONG:
337                         return this.getLong() == that.getLong();
338                     case PropertyType.DECIMAL:
339                         return this.getDecimal().equals(that.getDecimal());
340                     case PropertyType.DATE:
341                         return this.getDate().equals(that.getDate());
342                     case PropertyType.PATH:
343                         return this.getString().equals(that.getString());
344                     case PropertyType.NAME:
345                         return this.getString().equals(that.getString());
346                     case PropertyType.REFERENCE:
347                         return this.getString().equals(that.getString());
348                     default:
349                         throw new SystemFailureException(JcrI18n.invalidPropertyType.text(this.type));
350                 }
351             } catch (IOException e) {
352                 return false;
353             } catch (RepositoryException e) {
354                 return false;
355             }
356             // will not get here
357         }
358         return false;
359     }
360 
361     private JcrValue withTypeAndValue( int type,
362                                        Object value ) {
363         return new JcrValue(this.valueFactories, this.sessionCache, type, value);
364     }
365 
366     /**
367      * Returns a copy of the current {@link JcrValue} cast to the JCR type specified by the <code>type</code> argument. If the
368      * value cannot be converted base don the JCR type conversion rules, a {@link ValueFormatException} will be thrown.
369      * 
370      * @param type the JCR type from {@link PropertyType} that the new {@link JcrValue} should have.
371      * @return a new {@link JcrValue} with the given JCR type and an equivalent value.
372      * @throws ValueFormatException if the value contained by this {@link JcrValue} cannot be converted to the desired type.
373      * @see PropertyType
374      */
375     JcrValue asType( int type ) throws ValueFormatException {
376         if (type == this.type) {
377             return this.withTypeAndValue(this.type, this.value);
378         }
379 
380         Object value = this.value;
381         switch (type) {
382             case PropertyType.BOOLEAN:
383                 // Make sure the existing value is valid per the current type.
384                 // This is required if we rely upon the value factories to cast correctly.
385                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY) {
386                     throw createValueFormatException(boolean.class);
387                 }
388                 try {
389                     return this.withTypeAndValue(type, valueFactories.getBooleanFactory().create(value));
390                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
391                     throw createValueFormatException(vfe);
392                 }
393 
394             case PropertyType.DATE:
395                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.DOUBLE
396                     && this.type != PropertyType.LONG) {
397                     throw createValueFormatException(Calendar.class);
398                 }
399                 try {
400                     return this.withTypeAndValue(type, valueFactories.getDateFactory().create(value));
401                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
402                     throw createValueFormatException(vfe);
403                 }
404 
405             case PropertyType.NAME:
406                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.PATH) {
407                     throw createValueFormatException(Name.class);
408                 }
409                 try {
410                     return this.withTypeAndValue(type, valueFactories.getNameFactory().create(value));
411                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
412                     throw createValueFormatException(vfe);
413                 }
414 
415             case PropertyType.PATH:
416                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.NAME) {
417                     throw createValueFormatException(Path.class);
418                 }
419                 try {
420                     return this.withTypeAndValue(type, valueFactories.getPathFactory().create(value));
421                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
422                     throw createValueFormatException(vfe);
423                 }
424 
425             case PropertyType.REFERENCE:
426             case PropertyType.WEAKREFERENCE:
427                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY) {
428                     throw createValueFormatException(Node.class);
429                 }
430                 try {
431                     return this.withTypeAndValue(type, valueFactories.getReferenceFactory().create(value));
432                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
433                     throw createValueFormatException(vfe);
434                 }
435             case PropertyType.DOUBLE:
436                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.LONG
437                     && this.type != PropertyType.DATE) {
438                     throw createValueFormatException(double.class);
439                 }
440                 try {
441                     return this.withTypeAndValue(type, valueFactories.getDoubleFactory().create(value));
442                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
443                     throw createValueFormatException(vfe);
444                 }
445             case PropertyType.LONG:
446                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.DOUBLE
447                     && this.type != PropertyType.DATE) {
448                     throw createValueFormatException(long.class);
449                 }
450                 try {
451                     return this.withTypeAndValue(type, valueFactories.getLongFactory().create(value));
452                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
453                     throw createValueFormatException(vfe);
454                 }
455             case PropertyType.DECIMAL:
456                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.DOUBLE
457                     && this.type != PropertyType.LONG && this.type != PropertyType.DATE) {
458                     throw createValueFormatException(BigDecimal.class);
459                 }
460                 try {
461                     return this.withTypeAndValue(type, valueFactories.getDecimalFactory().create(value));
462                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
463                     throw createValueFormatException(vfe);
464                 }
465             case PropertyType.URI:
466                 if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.URI) {
467                     throw createValueFormatException(URI.class);
468                 }
469                 try {
470                     return this.withTypeAndValue(type, valueFactories.getUriFactory().create(value));
471                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
472                     throw createValueFormatException(vfe);
473                 }
474 
475                 // Anything can be converted to these types
476             case PropertyType.BINARY:
477                 try {
478                     return this.withTypeAndValue(type, valueFactories.getBinaryFactory().create(value));
479                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
480                     throw createValueFormatException(vfe);
481                 }
482             case PropertyType.STRING:
483                 try {
484                     return this.withTypeAndValue(type, valueFactories.getStringFactory().create(value));
485                 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
486                     throw createValueFormatException(vfe);
487                 }
488             case PropertyType.UNDEFINED:
489                 return this.withTypeAndValue(this.type, this.value);
490 
491             default:
492                 assert false : "Unexpected JCR property type " + type;
493                 // This should still throw an exception even if assertions are turned off
494                 throw new IllegalStateException("Invalid property type " + type);
495         }
496     }
497 
498     protected Object valueToType( int type,
499                                   Value value ) throws RepositoryException {
500         switch (type) {
501             case PropertyType.BOOLEAN:
502                 return valueFactories.getBooleanFactory().create(value.getBoolean());
503             case PropertyType.DATE:
504                 return valueFactories.getDateFactory().create(value.getDate());
505             case PropertyType.NAME:
506                 return valueFactories.getNameFactory().create(value.getString());
507             case PropertyType.PATH:
508                 return valueFactories.getPathFactory().create(value.getString());
509             case PropertyType.REFERENCE:
510             case PropertyType.WEAKREFERENCE:
511                 return valueFactories.getReferenceFactory().create(value.getString());
512             case PropertyType.DOUBLE:
513                 return valueFactories.getDoubleFactory().create(value.getDouble());
514             case PropertyType.LONG:
515                 return valueFactories.getLongFactory().create(value.getLong());
516             case PropertyType.DECIMAL:
517                 return valueFactories.getDecimalFactory().create(value.getDecimal());
518             case PropertyType.URI:
519                 return valueFactories.getUriFactory().create(value.getString());
520             case PropertyType.BINARY:
521                 return valueFactories.getBinaryFactory().create(value.getBinary());
522             case PropertyType.STRING:
523                 return valueFactories.getStringFactory().create(value.getString());
524             case PropertyType.UNDEFINED:
525                 return value.getString();
526 
527             default:
528                 assert false : "Unexpected JCR property type " + type;
529                 // This should still throw an exception even if assertions are turned off
530                 throw new IllegalStateException("Invalid property type " + type);
531         }
532     }
533 
534     @Override
535     public String toString() {
536         return (value == null ? "null" : value.toString()) + " (" + PropertyType.nameFromValue(type) + ")";
537     }
538 }