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.connector.store.jpa.util;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.ObjectInputStream;
29  import java.io.ObjectOutputStream;
30  import java.math.BigDecimal;
31  import java.net.URI;
32  import java.security.NoSuchAlgorithmException;
33  import java.util.Collection;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.UUID;
39  import org.modeshape.common.SystemFailureException;
40  import org.modeshape.common.util.SecureHash;
41  import org.modeshape.connector.store.jpa.model.basic.LargeValueEntity;
42  import org.modeshape.graph.ModeShapeLexicon;
43  import org.modeshape.graph.ExecutionContext;
44  import org.modeshape.graph.property.Binary;
45  import org.modeshape.graph.property.BinaryFactory;
46  import org.modeshape.graph.property.DateTime;
47  import org.modeshape.graph.property.Name;
48  import org.modeshape.graph.property.Path;
49  import org.modeshape.graph.property.Property;
50  import org.modeshape.graph.property.PropertyFactory;
51  import org.modeshape.graph.property.PropertyType;
52  import org.modeshape.graph.property.Reference;
53  import org.modeshape.graph.property.UuidFactory;
54  import org.modeshape.graph.property.ValueFactories;
55  import org.modeshape.graph.property.ValueFactory;
56  import org.modeshape.graph.property.ValueFormatException;
57  
58  /**
59   * A class that is responsible for serializing and deserializing properties.
60   */
61  public class Serializer {
62  
63      public static final LargeValues NO_LARGE_VALUES = new NoLargeValues();
64      public static final ReferenceValues NO_REFERENCES_VALUES = new NoReferenceValues();
65  
66      private final PropertyFactory propertyFactory;
67      private final ValueFactories valueFactories;
68      private final boolean excludeUuidProperty;
69  
70      public Serializer( ExecutionContext context,
71                         boolean excludeUuidProperty ) {
72          this.propertyFactory = context.getPropertyFactory();
73          this.valueFactories = context.getValueFactories();
74          this.excludeUuidProperty = excludeUuidProperty;
75      }
76  
77      /**
78       * Interface that represents the location where "large" objects are stored.
79       * 
80       * @author Randall Hauch
81       */
82      public interface LargeValues {
83          /**
84           * Get the minimum size for large values, specified as {@link String#length() number of characters} for a {@link String}
85           * or the {@link Binary#getSize() number of bytes for a binary value}
86           * 
87           * @return the size at which a property value is considered to be <i>large</i>
88           */
89          long getMinimumSize();
90  
91          void write( byte[] hash,
92                      long length,
93                      PropertyType type,
94                      Object value ) throws IOException;
95  
96          Object read( ValueFactories valueFactories,
97                       byte[] hash,
98                       long length ) throws IOException;
99      }
100 
101     protected static class NoLargeValues implements LargeValues {
102         public long getMinimumSize() {
103             return Long.MAX_VALUE;
104         }
105 
106         public void write( byte[] hash,
107                            long length,
108                            PropertyType type,
109                            Object value ) {
110         }
111 
112         public Object read( ValueFactories valueFactories,
113                             byte[] hash,
114                             long length ) {
115             return null;
116         }
117     }
118 
119     /**
120      * Interface used to record how Reference values are processed during serialization and deserialization.
121      * 
122      * @author Randall Hauch
123      */
124     public interface ReferenceValues {
125         void read( Reference reference );
126 
127         void write( Reference reference );
128 
129         void remove( Reference reference );
130     }
131 
132     protected static class NoReferenceValues implements ReferenceValues {
133         public void read( Reference arg0 ) {
134         }
135 
136         public void remove( Reference arg0 ) {
137         }
138 
139         public void write( Reference arg0 ) {
140         }
141     }
142 
143     /**
144      * Serialize the properties' values to the object stream.
145      * <p>
146      * If any of the property values are considered {@link LargeValues#getMinimumSize() large}, the value's hash and length of the
147      * property value will be written to the object stream, but the property value will be sent to the supplied
148      * {@link LargeValueEntity} object.
149      * </p>
150      * <p>
151      * This method does not automatically write each property value to the stream using
152      * {@link ObjectOutputStream#writeObject(Object)}, but instead serializes the primitive values that make up the property value
153      * object with a code that describes the {@link PropertyType property's type}. This is more efficient, since most of the
154      * property values are really non-primitive objects, and writing to the stream using
155      * {@link ObjectOutputStream#writeObject(Object)} would include larger class metadata.
156      * </p>
157      * 
158      * @param stream the stream where the properties' values are to be serialized; may not be null
159      * @param number the number of properties exposed by the supplied <code>properties</code> iterator; must be 0 or positive
160      * @param properties the iterator over the properties that are to be serialized; may not be null
161      * @param largeValues the interface to use for writing large values; may not be null
162      * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
163      *        null if the references do not need to be accumulated
164      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
165      * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
166      * @see #deserializeSomeProperties(ObjectInputStream, Collection, LargeValues, LargeValues, Name...)
167      * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
168      */
169     public void serializeProperties( ObjectOutputStream stream,
170                                      int number,
171                                      Iterable<Property> properties,
172                                      LargeValues largeValues,
173                                      ReferenceValues references ) throws IOException {
174         assert number >= 0;
175         assert properties != null;
176         assert largeValues != null;
177         stream.writeInt(number);
178         for (Property property : properties) {
179             if (property == null) continue;
180             serializeProperty(stream, property, largeValues, references);
181         }
182     }
183 
184     /**
185      * Serialize the property's values to the object stream.
186      * <p>
187      * If any of the property values are considered {@link LargeValues#getMinimumSize() large}, the value's hash and length of the
188      * property value will be written to the object stream, but the property value will be sent to the supplied
189      * {@link LargeValueEntity} object.
190      * </p>
191      * <p>
192      * This method does not automatically write each property value to the stream using
193      * {@link ObjectOutputStream#writeObject(Object)}, but instead serializes the primitive values that make up the property value
194      * object with a code that describes the {@link PropertyType property's type}. This is more efficient, since most of the
195      * property values are really non-primitive objects, and writing to the stream using
196      * {@link ObjectOutputStream#writeObject(Object)} would include larger class metadata.
197      * </p>
198      * 
199      * @param stream the stream where the property's values are to be serialized; may not be null
200      * @param property the property to be serialized; may not be null
201      * @param largeValues the interface to use for writing large values; may not be null
202      * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
203      *        null if the references do not need to be accumulated
204      * @return true if the property was serialized, or false if it was not
205      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
206      * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
207      * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
208      */
209     public boolean serializeProperty( ObjectOutputStream stream,
210                                       Property property,
211                                       LargeValues largeValues,
212                                       ReferenceValues references ) throws IOException {
213         assert stream != null;
214         assert property != null;
215         assert largeValues != null;
216         assert references != null;
217         final Name name = property.getName();
218         if (this.excludeUuidProperty && ModeShapeLexicon.UUID.equals(name)) return false;
219         // Write the name ...
220         stream.writeObject(name.getString(Path.NO_OP_ENCODER));
221         // Write the number of values ...
222         stream.writeInt(property.size());
223         for (Object value : property) {
224             if (value instanceof String) {
225                 String stringValue = (String)value;
226                 if (largeValues != null && stringValue.length() > largeValues.getMinimumSize()) {
227                     // Store the value in the large values area, but record the hash and length here.
228                     byte[] hash = computeHash(stringValue);
229                     stream.writeChar('L');
230                     stream.writeInt(hash.length);
231                     stream.write(hash);
232                     stream.writeLong(stringValue.length());
233                     // Now write to the large objects ...
234                     largeValues.write(computeHash(stringValue), stringValue.length(), PropertyType.STRING, stringValue);
235                 } else {
236                     stream.writeChar('S');
237                     stream.writeObject(stringValue);
238                 }
239             } else if (value instanceof Boolean) {
240                 stream.writeChar('b');
241                 stream.writeBoolean(((Boolean)value).booleanValue());
242             } else if (value instanceof Long) {
243                 stream.writeChar('l');
244                 stream.writeLong(((Long)value).longValue());
245             } else if (value instanceof Double) {
246                 stream.writeChar('d');
247                 stream.writeDouble(((Double)value).doubleValue());
248             } else if (value instanceof Integer) {
249                 stream.writeChar('i');
250                 stream.writeInt(((Integer)value).intValue());
251             } else if (value instanceof Short) {
252                 stream.writeChar('s');
253                 stream.writeShort(((Short)value).shortValue());
254             } else if (value instanceof Float) {
255                 stream.writeChar('f');
256                 stream.writeFloat(((Float)value).floatValue());
257             } else if (value instanceof UUID) {
258                 stream.writeChar('U');
259                 UUID uuid = (UUID)value;
260                 stream.writeLong(uuid.getMostSignificantBits());
261                 stream.writeLong(uuid.getLeastSignificantBits());
262             } else if (value instanceof URI) {
263                 URI uri = (URI)value;
264                 String stringValue = uri.toString();
265                 if (largeValues != null && stringValue.length() > largeValues.getMinimumSize()) {
266                     // Store the URI in the large values area, but record the hash and length here.
267                     byte[] hash = computeHash(stringValue);
268                     stream.writeChar('L');
269                     stream.writeInt(hash.length);
270                     stream.write(hash);
271                     stream.writeLong(stringValue.length());
272                     // Now write to the large objects ...
273                     largeValues.write(computeHash(stringValue), stringValue.length(), PropertyType.URI, stringValue);
274                 } else {
275                     stream.writeChar('I');
276                     stream.writeObject(stringValue);
277                 }
278             } else if (value instanceof Name) {
279                 stream.writeChar('N');
280                 stream.writeObject(((Name)value).getString(Path.NO_OP_ENCODER));
281             } else if (value instanceof Path) {
282                 stream.writeChar('P');
283                 stream.writeObject(((Path)value).getString());
284             } else if (value instanceof DateTime) {
285                 stream.writeChar('T');
286                 stream.writeObject(((DateTime)value).getString());
287             } else if (value instanceof BigDecimal) {
288                 stream.writeChar('D');
289                 stream.writeObject(value);
290             } else if (value instanceof Character) {
291                 stream.writeChar('c');
292                 char c = ((Character)value).charValue();
293                 stream.writeChar(c);
294             } else if (value instanceof Reference) {
295                 stream.writeChar('R');
296                 Reference ref = (Reference)value;
297                 stream.writeObject(ref.getString());
298                 references.write(ref);
299             } else if (value instanceof Binary) {
300                 Binary binary = (Binary)value;
301                 byte[] hash = null;
302                 long length = 0;
303                 try {
304                     binary.acquire();
305                     length = binary.getSize();
306                     if (largeValues != null && length > largeValues.getMinimumSize()) {
307                         // Store the value in the large values area, but record the hash and length here.
308                         hash = binary.getHash();
309                         stream.writeChar('L');
310                         stream.writeInt(hash.length);
311                         stream.write(hash);
312                         stream.writeLong(length);
313                         // Write to large objects after releasing the binary
314                     } else {
315                         // The value is small enough to store here ...
316                         stream.writeChar('B');
317                         stream.writeLong(length);
318                         InputStream data = binary.getStream();
319                         try {
320                             byte[] buffer = new byte[1024];
321                             int numRead = 0;
322                             while ((numRead = data.read(buffer)) > -1) {
323                                 stream.write(buffer, 0, numRead);
324                             }
325                         } finally {
326                             data.close();
327                         }
328                     }
329                 } finally {
330                     binary.release();
331                 }
332                 // If this is a large value and the binary has been released, write it to the large objects ...
333                 if (largeValues != null && hash != null) {
334                     largeValues.write(hash, length, PropertyType.BINARY, value);
335                 }
336             } else {
337                 // Other kinds of values ...
338                 stream.writeChar('O');
339                 stream.writeObject(value);
340             }
341         }
342         stream.flush();
343         return true;
344     }
345 
346     /**
347      * Deserialize the existing properties from the supplied input stream, update the properties, and then serialize the updated
348      * properties to the output stream.
349      * 
350      * @param input the stream from which the existing properties are to be deserialized; may not be null
351      * @param output the stream to which the updated properties are to be serialized; may not be null
352      * @param updatedProperties the properties that are being updated (or removed, if there are no values); may not be null
353      * @param largeValues the interface to use for writing large values; may not be null
354      * @param removedLargeValues the interface to use for recording the large values that were removed; may not be null
355      * @param createdProperties the set into which should be placed the names of the properties that were created; may not be null
356      * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
357      *        null if the references do not need to be accumulated
358      * @return the number of properties
359      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
360      * @throws ClassNotFoundException if the class for the value's object could not be found
361      */
362     public int reserializeProperties( ObjectInputStream input,
363                                       ObjectOutputStream output,
364                                       Map<Name, Property> updatedProperties,
365                                       LargeValues largeValues,
366                                       LargeValues removedLargeValues,
367                                       Set<Name> createdProperties,
368                                       ReferenceValues references ) throws IOException, ClassNotFoundException {
369         assert input != null;
370         assert output != null;
371         assert updatedProperties != null;
372         assert createdProperties != null;
373         assert largeValues != null;
374         assert references != null;
375         // Assemble a set of property names to skip deserializing
376         Map<Name, Property> allProperties = new HashMap<Name, Property>();
377 
378         // Start out by assuming that all properties are new ...
379         createdProperties.addAll(updatedProperties.keySet());
380 
381         // Read the number of properties ...
382         int count = input.readInt();
383         // Deserialize all of the properties ...
384         for (int i = 0; i != count; ++i) {
385             // Read the property name ...
386             String nameStr = (String)input.readObject();
387             Name name = valueFactories.getNameFactory().create(nameStr);
388             assert name != null;
389             if (updatedProperties.containsKey(name)) {
390                 // Deserialized, but don't materialize ...
391                 deserializePropertyValues(input, name, true, largeValues, removedLargeValues, references);
392             } else {
393                 // Now read the property values ...
394                 Object[] values = deserializePropertyValues(input, name, false, largeValues, removedLargeValues, references);
395                 // Add the property to the collection ...
396                 Property property = propertyFactory.create(name, values);
397                 assert property != null;
398                 allProperties.put(name, property);
399             }
400             // This is an existing property, so remove it from the set of created properties ...
401             createdProperties.remove(name);
402         }
403 
404         // Add all the updated properties ...
405         for (Map.Entry<Name, Property> entry : updatedProperties.entrySet()) {
406             Property updated = entry.getValue();
407             Name name = entry.getKey();
408             if (updated == null) {
409                 allProperties.remove(name);
410             } else {
411                 allProperties.put(name, updated);
412             }
413         }
414 
415         // Serialize properties ...
416         int numProperties = allProperties.size();
417         output.writeInt(numProperties);
418         for (Property property : allProperties.values()) {
419             if (property == null) continue;
420             serializeProperty(output, property, largeValues, references);
421         }
422         return numProperties;
423     }
424 
425     /**
426      * Deserialize the properties, adjust all {@link Reference} values that point to an "old" UUID to point to the corresponding
427      * "new" UUID, and reserialize the properties. If any reference is to a UUID not in the map, it is left untouched.
428      * <p>
429      * This is an efficient method that (for the most part) reads from the input stream and directly writes to the output stream.
430      * The exception is when a Reference value is read, that Reference is attempted to be remapped to a new Reference and written
431      * in place of the old reference. (Of course, if the Reference is to a UUID that is not in the "old" to "new" map, the old is
432      * written directly.)
433      * </p>
434      * 
435      * @param input the stream from which the existing properties are to be deserialized; may not be null
436      * @param output the stream to which the updated properties are to be serialized; may not be null
437      * @param oldUuidToNewUuid the map of old-to-new UUIDs; may not be null
438      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
439      * @throws ClassNotFoundException if the class for the value's object could not be found
440      */
441     public void adjustReferenceProperties( ObjectInputStream input,
442                                            ObjectOutputStream output,
443                                            Map<String, String> oldUuidToNewUuid ) throws IOException, ClassNotFoundException {
444         assert input != null;
445         assert output != null;
446         assert oldUuidToNewUuid != null;
447 
448         UuidFactory uuidFactory = valueFactories.getUuidFactory();
449         ValueFactory<Reference> referenceFactory = valueFactories.getReferenceFactory();
450 
451         // Read the number of properties ...
452         int count = input.readInt();
453         output.writeInt(count);
454         // Deserialize all of the proeprties ...
455         for (int i = 0; i != count; ++i) {
456             // Read and write the property name ...
457             Object name = input.readObject();
458             output.writeObject(name);
459             // Read and write the number of values ...
460             int numValues = input.readInt();
461             output.writeInt(numValues);
462             // Now read and write each property value ...
463             for (int j = 0; j != numValues; ++j) {
464                 // Read and write the type of value ...
465                 char type = input.readChar();
466                 output.writeChar(type);
467                 switch (type) {
468                     case 'S':
469                         output.writeObject(input.readObject());
470                         break;
471                     case 'b':
472                         output.writeBoolean(input.readBoolean());
473                         break;
474                     case 'i':
475                         output.writeInt(input.readInt());
476                         break;
477                     case 'l':
478                         output.writeLong(input.readLong());
479                         break;
480                     case 's':
481                         output.writeShort(input.readShort());
482                         break;
483                     case 'f':
484                         output.writeFloat(input.readFloat());
485                         break;
486                     case 'd':
487                         output.writeDouble(input.readDouble());
488                         break;
489                     case 'c':
490                         // char
491                         output.writeChar(input.readChar());
492                         break;
493                     case 'U':
494                         // UUID
495                         output.writeLong(input.readLong());
496                         output.writeLong(input.readLong());
497                         break;
498                     case 'I':
499                         // URI
500                         output.writeObject(input.readObject());
501                         break;
502                     case 'N':
503                         // Name
504                         output.writeObject(input.readObject());
505                         break;
506                     case 'P':
507                         // Path
508                         output.writeObject(input.readObject());
509                         break;
510                     case 'T':
511                         // DateTime
512                         output.writeObject(input.readObject());
513                         break;
514                     case 'D':
515                         // BigDecimal
516                         output.writeObject(input.readObject());
517                         break;
518                     case 'R':
519                         // Reference
520                         String refValue = (String)input.readObject();
521                         Reference ref = referenceFactory.create(refValue);
522                         try {
523                             UUID toUuid = uuidFactory.create(ref);
524                             String newUuid = oldUuidToNewUuid.get(toUuid.toString());
525                             if (newUuid != null) {
526                                 // Create a new reference ...
527                                 ref = referenceFactory.create(newUuid);
528                                 refValue = ref.getString();
529                             }
530                         } catch (ValueFormatException e) {
531                             // Unknown reference, so simply write it again ...
532                         }
533                         // Write the reference ...
534                         output.writeObject(refValue);
535                         break;
536                     case 'B':
537                         // Binary
538                         // Read the length of the content ...
539                         long binaryLength = input.readLong();
540                         byte[] content = new byte[(int)binaryLength];
541                         input.read(content);
542                         // Now write out the value ...
543                         output.writeLong(binaryLength);
544                         output.write(content);
545                         break;
546                     case 'L':
547                         // Large object ...
548                         int hashLength = input.readInt();
549                         byte[] hash = new byte[hashLength];
550                         input.read(hash);
551                         long length = input.readLong();
552                         // write to the output ...
553                         output.writeInt(hash.length);
554                         output.write(hash);
555                         output.writeLong(length);
556                         break;
557                     default:
558                         // All other objects ...
559                         output.writeObject(input.readObject());
560                         break;
561                 }
562             }
563         }
564     }
565 
566     /**
567      * Deserialize the serialized properties on the supplied object stream.
568      * 
569      * @param stream the stream that contains the serialized properties; may not be null
570      * @param properties the collection into which each deserialized property is to be placed; may not be null
571      * @param largeValues the interface to use for writing large values; may not be null
572      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
573      * @throws ClassNotFoundException if the class for the value's object could not be found
574      * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
575      * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
576      */
577     public void deserializeAllProperties( ObjectInputStream stream,
578                                           Collection<Property> properties,
579                                           LargeValues largeValues ) throws IOException, ClassNotFoundException {
580         assert stream != null;
581         assert properties != null;
582         // Read the number of properties ...
583         int count = stream.readInt();
584         for (int i = 0; i != count; ++i) {
585             Property property = deserializeProperty(stream, largeValues);
586             assert property != null;
587             properties.add(property);
588         }
589     }
590 
591     /**
592      * Deserialize the serialized properties on the supplied object stream.
593      * 
594      * @param stream the stream that contains the serialized properties; may not be null
595      * @param properties the collection into which each deserialized property is to be placed; may not be null
596      * @param names the names of the properties that should be deserialized; should not be null or empty
597      * @param largeValues the interface to use for writing large values; may not be null
598      * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
599      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
600      * @throws ClassNotFoundException if the class for the value's object could not be found
601      * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
602      * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
603      */
604     public void deserializeSomeProperties( ObjectInputStream stream,
605                                            Collection<Property> properties,
606                                            LargeValues largeValues,
607                                            LargeValues skippedLargeValues,
608                                            Name... names ) throws IOException, ClassNotFoundException {
609         assert stream != null;
610         assert properties != null;
611         assert names != null;
612         assert names.length > 0;
613         Name nameToRead = null;
614         Set<Name> namesToRead = null;
615         if (names.length == 1) {
616             nameToRead = names[0];
617         } else {
618             namesToRead = new HashSet<Name>();
619             for (Name name : names) {
620                 if (name != null) namesToRead.add(name);
621             }
622         }
623 
624         // Read the number of properties ...
625         boolean read = false;
626         int count = stream.readInt();
627 
628         // Now, read the properties (or skip the ones that we're not supposed to read) ...
629         for (int i = 0; i != count; ++i) {
630             // Read the name ...
631             String nameStr = (String)stream.readObject();
632             Name name = valueFactories.getNameFactory().create(nameStr);
633             assert name != null;
634             read = name.equals(nameToRead) || (namesToRead != null && namesToRead.contains(namesToRead));
635             if (read) {
636                 // Now read the property values ...
637                 Object[] values = deserializePropertyValues(stream, name, false, largeValues, skippedLargeValues, null);
638                 // Add the property to the collection ...
639                 Property property = propertyFactory.create(name, values);
640                 assert property != null;
641                 properties.add(property);
642             } else {
643                 // Skip the property ...
644                 deserializePropertyValues(stream, name, true, largeValues, skippedLargeValues, null);
645             }
646         }
647     }
648 
649     /**
650      * Deserialize the serialized property on the supplied object stream.
651      * 
652      * @param stream the stream that contains the serialized properties; may not be null
653      * @param largeValues the interface to use for writing large values; may not be null
654      * @return the deserialized property; never null
655      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
656      * @throws ClassNotFoundException if the class for the value's object could not be found
657      * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
658      * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
659      */
660     public Property deserializeProperty( ObjectInputStream stream,
661                                          LargeValues largeValues ) throws IOException, ClassNotFoundException {
662         // Read the name ...
663         String nameStr = (String)stream.readObject();
664         Name name = valueFactories.getNameFactory().create(nameStr);
665         assert name != null;
666         // Now read the property values ...
667         Object[] values = deserializePropertyValues(stream, name, false, largeValues, largeValues, null);
668         // Add the property to the collection ...
669         return propertyFactory.create(name, values);
670     }
671 
672     /**
673      * Deserialize the serialized property on the supplied object stream.
674      * 
675      * @param stream the stream that contains the serialized properties; may not be null
676      * @param propertyName the name of the property being deserialized
677      * @param skip true if the values don't need to be read, or false if they are to be read
678      * @param largeValues the interface to use for writing large values; may not be null
679      * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
680      * @param references the interface to use for recording which {@link Reference} values were found (and/or removed) during
681      *        deserialization; may not be null
682      * @return the deserialized property values, or an empty list if there are no values
683      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
684      * @throws ClassNotFoundException if the class for the value's object could not be found
685      * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
686      * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
687      */
688     public Object[] deserializePropertyValues( ObjectInputStream stream,
689                                                Name propertyName,
690                                                boolean skip,
691                                                LargeValues largeValues,
692                                                LargeValues skippedLargeValues,
693                                                ReferenceValues references ) throws IOException, ClassNotFoundException {
694         assert stream != null;
695         assert propertyName != null;
696         assert largeValues != null;
697         assert skippedLargeValues != null;
698         // Read the number of values ...
699         int size = stream.readInt();
700         Object[] values = skip ? null : new Object[size];
701         for (int i = 0; i != size; ++i) {
702             Object value = null;
703             // Read the type of value ...
704             char type = stream.readChar();
705             switch (type) {
706                 case 'S':
707                     String stringValue = (String)stream.readObject();
708                     if (!skip) value = valueFactories.getStringFactory().create(stringValue);
709                     break;
710                 case 'b':
711                     boolean booleanValue = stream.readBoolean();
712                     if (!skip) value = valueFactories.getBooleanFactory().create(booleanValue);
713                     break;
714                 case 'i':
715                     int intValue = stream.readInt();
716                     if (!skip) value = valueFactories.getLongFactory().create(intValue);
717                     break;
718                 case 'l':
719                     long longValue = stream.readLong();
720                     if (!skip) value = valueFactories.getLongFactory().create(longValue);
721                     break;
722                 case 's':
723                     short shortValue = stream.readShort();
724                     if (!skip) value = valueFactories.getLongFactory().create(shortValue);
725                     break;
726                 case 'f':
727                     float floatValue = stream.readFloat();
728                     if (!skip) value = valueFactories.getDoubleFactory().create(floatValue);
729                     break;
730                 case 'd':
731                     double doubleValue = stream.readDouble();
732                     if (!skip) value = valueFactories.getDoubleFactory().create(doubleValue);
733                     break;
734                 case 'c':
735                     // char
736                     String charValue = "" + stream.readChar();
737                     if (!skip) value = valueFactories.getStringFactory().create(charValue);
738                     break;
739                 case 'U':
740                     // UUID
741                     long msb = stream.readLong();
742                     long lsb = stream.readLong();
743                     if (!skip) {
744                         UUID uuid = new UUID(msb, lsb);
745                         value = valueFactories.getUuidFactory().create(uuid);
746                     }
747                     break;
748                 case 'I':
749                     // URI
750                     String uriStr = (String)stream.readObject();
751                     if (!skip) value = valueFactories.getUriFactory().create(uriStr);
752                     break;
753                 case 'N':
754                     // Name
755                     String nameValueStr = (String)stream.readObject();
756                     if (!skip) value = valueFactories.getNameFactory().create(nameValueStr);
757                     break;
758                 case 'P':
759                     // Path
760                     String pathStr = (String)stream.readObject();
761                     if (!skip) value = valueFactories.getPathFactory().create(pathStr);
762                     break;
763                 case 'T':
764                     // DateTime
765                     String dateTimeStr = (String)stream.readObject();
766                     if (!skip) value = valueFactories.getDateFactory().create(dateTimeStr);
767                     break;
768                 case 'D':
769                     // BigDecimal
770                     Object bigDecimal = stream.readObject();
771                     if (!skip) value = valueFactories.getDecimalFactory().create(bigDecimal);
772                     break;
773                 case 'R':
774                     // Reference
775                     String refValue = (String)stream.readObject();
776                     Reference ref = valueFactories.getReferenceFactory().create(refValue);
777                     if (skip) {
778                         if (references != null) references.remove(ref);
779                     } else {
780                         value = ref;
781                         if (references != null) references.read(ref);
782                     }
783                     break;
784                 case 'B':
785                     // Binary
786                     // Read the length of the content ...
787                     long binaryLength = stream.readLong();
788                     byte[] content = new byte[(int)binaryLength];
789                     stream.readFully(content, 0, content.length);
790                     if (!skip) {
791                         value = valueFactories.getBinaryFactory().create(content);
792                     }
793                     break;
794                 case 'L':
795                     // Large object ...
796                     // Read the hash ...
797                     int hashLength = stream.readInt();
798                     byte[] hash = new byte[hashLength];
799                     stream.readFully(hash, 0, hashLength);
800                     // Read the length of the content ...
801                     long length = stream.readLong();
802                     if (skip) {
803                         skippedLargeValues.read(valueFactories, hash, length);
804                     } else {
805                         BinaryFactory factory = valueFactories.getBinaryFactory();
806                         // Look for an already-loaded Binary value with the same hash ...
807                         value = factory.find(hash);
808                         if (value == null) {
809                             // Didn't find an existing large value, so we have to read the large value ...
810                             value = largeValues.read(valueFactories, hash, length);
811                         }
812                     }
813                     break;
814                 default:
815                     // All other objects ...
816                     Object object = stream.readObject();
817                     if (!skip) value = valueFactories.getObjectFactory().create(object);
818                     break;
819             }
820             if (value != null) values[i] = value;
821         }
822         return values;
823     }
824 
825     public byte[] computeHash( String value ) {
826         try {
827             return SecureHash.getHash(SecureHash.Algorithm.SHA_1, value.getBytes());
828         } catch (NoSuchAlgorithmException e) {
829             throw new SystemFailureException(e);
830         }
831     }
832 }