001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.connector.store.jpa.util;
025    
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.io.ObjectInputStream;
029    import java.io.ObjectOutputStream;
030    import java.math.BigDecimal;
031    import java.net.URI;
032    import java.security.NoSuchAlgorithmException;
033    import java.util.Collection;
034    import java.util.Collections;
035    import java.util.HashMap;
036    import java.util.HashSet;
037    import java.util.Map;
038    import java.util.Set;
039    import java.util.UUID;
040    import org.jboss.dna.common.SystemFailureException;
041    import org.jboss.dna.common.util.SecureHash;
042    import org.jboss.dna.connector.store.jpa.model.basic.LargeValueEntity;
043    import org.jboss.dna.graph.DnaLexicon;
044    import org.jboss.dna.graph.ExecutionContext;
045    import org.jboss.dna.graph.property.Binary;
046    import org.jboss.dna.graph.property.BinaryFactory;
047    import org.jboss.dna.graph.property.DateTime;
048    import org.jboss.dna.graph.property.Name;
049    import org.jboss.dna.graph.property.Path;
050    import org.jboss.dna.graph.property.Property;
051    import org.jboss.dna.graph.property.PropertyFactory;
052    import org.jboss.dna.graph.property.PropertyType;
053    import org.jboss.dna.graph.property.Reference;
054    import org.jboss.dna.graph.property.UuidFactory;
055    import org.jboss.dna.graph.property.ValueFactories;
056    import org.jboss.dna.graph.property.ValueFactory;
057    import org.jboss.dna.graph.property.ValueFormatException;
058    
059    /**
060     * @author Randall Hauch
061     */
062    public class Serializer {
063    
064        public static final LargeValues NO_LARGE_VALUES = new NoLargeValues();
065        public static final ReferenceValues NO_REFERENCES_VALUES = new NoReferenceValues();
066    
067        private final PropertyFactory propertyFactory;
068        private final ValueFactories valueFactories;
069        private final boolean excludeUuidProperty;
070    
071        public Serializer( ExecutionContext context,
072                           boolean excludeUuidProperty ) {
073            this.propertyFactory = context.getPropertyFactory();
074            this.valueFactories = context.getValueFactories();
075            this.excludeUuidProperty = excludeUuidProperty;
076        }
077    
078        /**
079         * Interface that represents the location where "large" objects are stored.
080         * 
081         * @author Randall Hauch
082         */
083        public interface LargeValues {
084            /**
085             * Get the minimum size for large values, specified as {@link String#length() number of characters} for a {@link String}
086             * or the {@link Binary#getSize() number of bytes for a binary value}
087             * 
088             * @return the size at which a property value is considered to be <i>large</i>
089             */
090            long getMinimumSize();
091    
092            void write( byte[] hash,
093                        long length,
094                        PropertyType type,
095                        Object value ) throws IOException;
096    
097            Object read( ValueFactories valueFactories,
098                         byte[] hash,
099                         long length ) throws IOException;
100        }
101    
102        protected static class NoLargeValues implements LargeValues {
103            public long getMinimumSize() {
104                return Long.MAX_VALUE;
105            }
106    
107            public void write( byte[] hash,
108                               long length,
109                               PropertyType type,
110                               Object value ) {
111            }
112    
113            public Object read( ValueFactories valueFactories,
114                                byte[] hash,
115                                long length ) {
116                return null;
117            }
118        }
119    
120        /**
121         * Interface used to record how Reference values are processed during serialization and deserialization.
122         * 
123         * @author Randall Hauch
124         */
125        public interface ReferenceValues {
126            void read( Reference reference );
127    
128            void write( Reference reference );
129    
130            void remove( Reference reference );
131        }
132    
133        protected static class NoReferenceValues implements ReferenceValues {
134            public void read( Reference arg0 ) {
135            }
136    
137            public void remove( Reference arg0 ) {
138            }
139    
140            public void write( Reference arg0 ) {
141            }
142        }
143    
144        /**
145         * Serialize the properties' values to the object stream.
146         * <p>
147         * If any of the property values are considered {@link LargeValues#getMinimumSize() large}, the value's hash and length of the
148         * property value will be written to the object stream, but the property value will be sent to the supplied
149         * {@link LargeValueEntity} object.
150         * </p>
151         * <p>
152         * This method does not automatically write each property value to the stream using
153         * {@link ObjectOutputStream#writeObject(Object)}, but instead serializes the primitive values that make up the property value
154         * object with a code that describes the {@link PropertyType property's type}. This is more efficient, since most of the
155         * property values are really non-primitive objects, and writing to the stream using
156         * {@link ObjectOutputStream#writeObject(Object)} would include larger class metadata.
157         * </p>
158         * 
159         * @param stream the stream where the properties' values are to be serialized; may not be null
160         * @param number the number of properties exposed by the supplied <code>properties</code> iterator; must be 0 or positive
161         * @param properties the iterator over the properties that are to be serialized; may not be null
162         * @param largeValues the interface to use for writing large values; may not be null
163         * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
164         *        null if the references do not need to be accumulated
165         * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
166         * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
167         * @see #deserializeSomeProperties(ObjectInputStream, Collection, LargeValues, LargeValues, Name...)
168         * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
169         */
170        public void serializeProperties( ObjectOutputStream stream,
171                                         int number,
172                                         Iterable<Property> properties,
173                                         LargeValues largeValues,
174                                         ReferenceValues references ) throws IOException {
175            assert number >= 0;
176            assert properties != null;
177            assert largeValues != null;
178            stream.writeInt(number);
179            for (Property property : properties) {
180                if (property == null) continue;
181                serializeProperty(stream, property, largeValues, references);
182            }
183        }
184    
185        /**
186         * Serialize the property's values to the object stream.
187         * <p>
188         * If any of the property values are considered {@link LargeValues#getMinimumSize() large}, the value's hash and length of the
189         * property value will be written to the object stream, but the property value will be sent to the supplied
190         * {@link LargeValueEntity} object.
191         * </p>
192         * <p>
193         * This method does not automatically write each property value to the stream using
194         * {@link ObjectOutputStream#writeObject(Object)}, but instead serializes the primitive values that make up the property value
195         * object with a code that describes the {@link PropertyType property's type}. This is more efficient, since most of the
196         * property values are really non-primitive objects, and writing to the stream using
197         * {@link ObjectOutputStream#writeObject(Object)} would include larger class metadata.
198         * </p>
199         * 
200         * @param stream the stream where the property's values are to be serialized; may not be null
201         * @param property the property to be serialized; may not be null
202         * @param largeValues the interface to use for writing large values; may not be null
203         * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
204         *        null if the references do not need to be accumulated
205         * @return true if the property was serialized, or false if it was not
206         * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
207         * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
208         * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
209         */
210        public boolean serializeProperty( ObjectOutputStream stream,
211                                          Property property,
212                                          LargeValues largeValues,
213                                          ReferenceValues references ) throws IOException {
214            assert stream != null;
215            assert property != null;
216            assert largeValues != null;
217            assert references != null;
218            final Name name = property.getName();
219            if (this.excludeUuidProperty && DnaLexicon.UUID.equals(name)) return false;
220            // Write the name ...
221            stream.writeObject(name.getString());
222            // Write the number of values ...
223            stream.writeInt(property.size());
224            for (Object value : property) {
225                if (value instanceof String) {
226                    String stringValue = (String)value;
227                    if (largeValues != null && stringValue.length() > largeValues.getMinimumSize()) {
228                        // Store the value in the large values area, but record the hash and length here.
229                        byte[] hash = computeHash(stringValue);
230                        stream.writeChar('L');
231                        stream.writeInt(hash.length);
232                        stream.write(hash);
233                        stream.writeLong(stringValue.length());
234                        // Now write to the large objects ...
235                        largeValues.write(computeHash(stringValue), stringValue.length(), PropertyType.STRING, stringValue);
236                    } else {
237                        stream.writeChar('S');
238                        stream.writeObject(stringValue);
239                    }
240                } else if (value instanceof Boolean) {
241                    stream.writeChar('b');
242                    stream.writeBoolean(((Boolean)value).booleanValue());
243                } else if (value instanceof Long) {
244                    stream.writeChar('l');
245                    stream.writeLong(((Long)value).longValue());
246                } else if (value instanceof Double) {
247                    stream.writeChar('d');
248                    stream.writeDouble(((Double)value).doubleValue());
249                } else if (value instanceof Integer) {
250                    stream.writeChar('i');
251                    stream.writeInt(((Integer)value).intValue());
252                } else if (value instanceof Short) {
253                    stream.writeChar('s');
254                    stream.writeShort(((Short)value).shortValue());
255                } else if (value instanceof Float) {
256                    stream.writeChar('f');
257                    stream.writeFloat(((Float)value).floatValue());
258                } else if (value instanceof UUID) {
259                    stream.writeChar('U');
260                    UUID uuid = (UUID)value;
261                    stream.writeLong(uuid.getMostSignificantBits());
262                    stream.writeLong(uuid.getLeastSignificantBits());
263                } else if (value instanceof URI) {
264                    URI uri = (URI)value;
265                    String stringValue = uri.toString();
266                    if (largeValues != null && stringValue.length() > largeValues.getMinimumSize()) {
267                        // Store the URI in the large values area, but record the hash and length here.
268                        byte[] hash = computeHash(stringValue);
269                        stream.writeChar('L');
270                        stream.writeInt(hash.length);
271                        stream.write(hash);
272                        stream.writeLong(stringValue.length());
273                        // Now write to the large objects ...
274                        largeValues.write(computeHash(stringValue), stringValue.length(), PropertyType.URI, stringValue);
275                    } else {
276                        stream.writeChar('I');
277                        stream.writeObject(stringValue);
278                    }
279                } else if (value instanceof Name) {
280                    stream.writeChar('N');
281                    stream.writeObject(((Name)value).getString());
282                } else if (value instanceof Path) {
283                    stream.writeChar('P');
284                    stream.writeObject(((Path)value).getString());
285                } else if (value instanceof DateTime) {
286                    stream.writeChar('T');
287                    stream.writeObject(((DateTime)value).getString());
288                } else if (value instanceof BigDecimal) {
289                    stream.writeChar('D');
290                    stream.writeObject(value);
291                } else if (value instanceof Character) {
292                    stream.writeChar('c');
293                    char c = ((Character)value).charValue();
294                    stream.writeChar(c);
295                } else if (value instanceof Reference) {
296                    stream.writeChar('R');
297                    Reference ref = (Reference)value;
298                    stream.writeObject(ref.getString());
299                    references.write(ref);
300                } else if (value instanceof Binary) {
301                    Binary binary = (Binary)value;
302                    byte[] hash = null;
303                    long length = 0;
304                    try {
305                        binary.acquire();
306                        length = binary.getSize();
307                        if (largeValues != null && length > largeValues.getMinimumSize()) {
308                            // Store the value in the large values area, but record the hash and length here.
309                            hash = binary.getHash();
310                            stream.writeChar('L');
311                            stream.writeInt(hash.length);
312                            stream.write(hash);
313                            stream.writeLong(length);
314                            // Write to large objects after releasing the binary
315                        } else {
316                            // The value is small enough to store here ...
317                            stream.writeChar('B');
318                            stream.writeLong(length);
319                            InputStream data = binary.getStream();
320                            try {
321                                byte[] buffer = new byte[1024];
322                                int numRead = 0;
323                                while ((numRead = data.read(buffer)) > -1) {
324                                    stream.write(buffer, 0, numRead);
325                                }
326                            } finally {
327                                data.close();
328                            }
329                        }
330                    } finally {
331                        binary.release();
332                    }
333                    // If this is a large value and the binary has been released, write it to the large objects ...
334                    if (largeValues != null && hash != null) {
335                        largeValues.write(hash, length, PropertyType.BINARY, value);
336                    }
337                } else {
338                    // Other kinds of values ...
339                    stream.writeChar('O');
340                    stream.writeObject(value);
341                }
342            }
343            stream.flush();
344            return true;
345        }
346    
347        /**
348         * Deserialize the existing properties from the supplied input stream, update the properties, and then serialize the updated
349         * properties to the output stream.
350         * 
351         * @param input the stream from which the existing properties are to be deserialized; may not be null
352         * @param output the stream to which the updated properties are to be serialized; may not be null
353         * @param updatedProperties the properties that are being updated (or removed, if there are no values); may not be null
354         * @param largeValues the interface to use for writing large values; may not be null
355         * @param removedLargeValues the interface to use for recording the large values that were removed; 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                                          ReferenceValues references ) throws IOException, ClassNotFoundException {
368            assert input != null;
369            assert output != null;
370            assert updatedProperties != null;
371            assert largeValues != null;
372            assert references != null;
373            // Assemble a set of property names to skip deserializing
374            Map<Name, Property> allProperties = new HashMap<Name, Property>();
375    
376            // Read the number of properties ...
377            int count = input.readInt();
378            // Deserialize all of the proeprties ...
379            for (int i = 0; i != count; ++i) {
380                // Read the property name ...
381                String nameStr = (String)input.readObject();
382                Name name = valueFactories.getNameFactory().create(nameStr);
383                assert name != null;
384                if (updatedProperties.containsKey(name)) {
385                    // Deserialized, but don't materialize ...
386                    deserializePropertyValues(input, name, true, largeValues, removedLargeValues, references);
387                } else {
388                    // Now read the property values ...
389                    Object[] values = deserializePropertyValues(input, name, false, largeValues, removedLargeValues, references);
390                    // Add the property to the collection ...
391                    Property property = propertyFactory.create(name, values);
392                    assert property != null;
393                    allProperties.put(name, property);
394                }
395            }
396    
397            // Add all the updated properties ...
398            for (Map.Entry<Name, Property> entry : updatedProperties.entrySet()) {
399                Property updated = entry.getValue();
400                if (updated == null) {
401                    allProperties.remove(entry.getKey());
402                } else {
403                    allProperties.put(updated.getName(), updated);
404                }
405            }
406    
407            // Serialize properties ...
408            int numProperties = allProperties.size();
409            output.writeInt(numProperties);
410            for (Property property : allProperties.values()) {
411                if (property == null) continue;
412                serializeProperty(output, property, largeValues, references);
413            }
414            return numProperties;
415        }
416    
417        /**
418         * Deserialize the properties, adjust all {@link Reference} values that point to an "old" UUID to point to the corresponding
419         * "new" UUID, and reserialize the properties. If any reference is to a UUID not in the map, it is left untouched.
420         * <p>
421         * This is an efficient method that (for the most part) reads from the input stream and directly writes to the output stream.
422         * The exception is when a Reference value is read, that Reference is attempted to be remapped to a new Reference and written
423         * 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
424         * written directly.)
425         * </p>
426         * 
427         * @param input the stream from which the existing properties are to be deserialized; may not be null
428         * @param output the stream to which the updated properties are to be serialized; may not be null
429         * @param oldUuidToNewUuid the map of old-to-new UUIDs
430         * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
431         * @throws ClassNotFoundException if the class for the value's object could not be found
432         */
433        public void adjustReferenceProperties( ObjectInputStream input,
434                                               ObjectOutputStream output,
435                                               Map<String, String> oldUuidToNewUuid ) throws IOException, ClassNotFoundException {
436            assert input != null;
437            assert output != null;
438            if (oldUuidToNewUuid == null) oldUuidToNewUuid = Collections.emptyMap();
439    
440            UuidFactory uuidFactory = valueFactories.getUuidFactory();
441            ValueFactory<Reference> referenceFactory = valueFactories.getReferenceFactory();
442    
443            // Read the number of properties ...
444            int count = input.readInt();
445            output.writeInt(count);
446            // Deserialize all of the proeprties ...
447            for (int i = 0; i != count; ++i) {
448                // Read and write the property name ...
449                Object name = input.readObject();
450                output.writeObject(name);
451                // Read and write the number of values ...
452                int numValues = input.readInt();
453                output.writeInt(numValues);
454                // Now read and write each property value ...
455                for (int j = 0; j != numValues; ++j) {
456                    // Read and write the type of value ...
457                    char type = input.readChar();
458                    output.writeChar(type);
459                    switch (type) {
460                        case 'S':
461                            output.writeObject(input.readObject());
462                            break;
463                        case 'b':
464                            output.writeBoolean(input.readBoolean());
465                            break;
466                        case 'i':
467                            output.writeInt(input.readInt());
468                            break;
469                        case 'l':
470                            output.writeLong(input.readLong());
471                            break;
472                        case 's':
473                            output.writeShort(input.readShort());
474                            break;
475                        case 'f':
476                            output.writeFloat(input.readFloat());
477                            break;
478                        case 'd':
479                            output.writeDouble(input.readDouble());
480                            break;
481                        case 'c':
482                            // char
483                            output.writeChar(input.readChar());
484                            break;
485                        case 'U':
486                            // UUID
487                            output.writeLong(input.readLong());
488                            output.writeLong(input.readLong());
489                            break;
490                        case 'I':
491                            // URI
492                            output.writeObject(input.readObject());
493                            break;
494                        case 'N':
495                            // Name
496                            output.writeObject(input.readObject());
497                            break;
498                        case 'P':
499                            // Path
500                            output.writeObject(input.readObject());
501                            break;
502                        case 'T':
503                            // DateTime
504                            output.writeObject(input.readObject());
505                            break;
506                        case 'D':
507                            // BigDecimal
508                            output.writeObject(input.readObject());
509                            break;
510                        case 'R':
511                            // Reference
512                            String refValue = (String)input.readObject();
513                            Reference ref = referenceFactory.create(refValue);
514                            try {
515                                UUID toUuid = uuidFactory.create(ref);
516                                String newUuid = oldUuidToNewUuid.get(toUuid.toString());
517                                if (newUuid != null) {
518                                    // Create a new reference ...
519                                    ref = referenceFactory.create(newUuid);
520                                    refValue = ref.getString();
521                                }
522                            } catch (ValueFormatException e) {
523                                // Unknown reference, so simply write it again ...
524                            }
525                            // Write the reference ...
526                            output.writeObject(refValue);
527                            break;
528                        case 'B':
529                            // Binary
530                            // Read the length of the content ...
531                            long binaryLength = input.readLong();
532                            byte[] content = new byte[(int)binaryLength];
533                            input.read(content);
534                            // Now write out the value ...
535                            output.writeLong(binaryLength);
536                            output.write(content);
537                            break;
538                        case 'L':
539                            // Large object ...
540                            int hashLength = input.readInt();
541                            byte[] hash = new byte[hashLength];
542                            input.read(hash);
543                            long length = input.readLong();
544                            // write to the output ...
545                            output.writeInt(hash.length);
546                            output.write(hash);
547                            output.writeLong(length);
548                            break;
549                        default:
550                            // All other objects ...
551                            output.writeObject(input.readObject());
552                            break;
553                    }
554                }
555            }
556        }
557    
558        /**
559         * Deserialize the serialized properties on the supplied object stream.
560         * 
561         * @param stream the stream that contains the serialized properties; may not be null
562         * @param properties the collection into which each deserialized property is to be placed; may not be null
563         * @param largeValues the interface to use for writing large values; may not be null
564         * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
565         * @throws ClassNotFoundException if the class for the value's object could not be found
566         * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
567         * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
568         */
569        public void deserializeAllProperties( ObjectInputStream stream,
570                                              Collection<Property> properties,
571                                              LargeValues largeValues ) throws IOException, ClassNotFoundException {
572            assert stream != null;
573            assert properties != null;
574            // Read the number of properties ...
575            int count = stream.readInt();
576            for (int i = 0; i != count; ++i) {
577                Property property = deserializeProperty(stream, largeValues);
578                assert property != null;
579                properties.add(property);
580            }
581        }
582    
583        /**
584         * Deserialize the serialized properties on the supplied object stream.
585         * 
586         * @param stream the stream that contains the serialized properties; may not be null
587         * @param properties the collection into which each deserialized property is to be placed; may not be null
588         * @param names the names of the properties that should be deserialized; should not be null or empty
589         * @param largeValues the interface to use for writing large values; may not be null
590         * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
591         * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
592         * @throws ClassNotFoundException if the class for the value's object could not be found
593         * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
594         * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
595         */
596        public void deserializeSomeProperties( ObjectInputStream stream,
597                                               Collection<Property> properties,
598                                               LargeValues largeValues,
599                                               LargeValues skippedLargeValues,
600                                               Name... names ) throws IOException, ClassNotFoundException {
601            assert stream != null;
602            assert properties != null;
603            assert names != null;
604            assert names.length > 0;
605            Name nameToRead = null;
606            Set<Name> namesToRead = null;
607            if (names.length == 1) {
608                nameToRead = names[0];
609            } else {
610                namesToRead = new HashSet<Name>();
611                for (Name name : names) {
612                    if (name != null) namesToRead.add(name);
613                }
614            }
615    
616            // Read the number of properties ...
617            boolean read = false;
618            int count = stream.readInt();
619    
620            // Now, read the properties (or skip the ones that we're not supposed to read) ...
621            for (int i = 0; i != count; ++i) {
622                // Read the name ...
623                String nameStr = (String)stream.readObject();
624                Name name = valueFactories.getNameFactory().create(nameStr);
625                assert name != null;
626                read = name.equals(nameToRead) || (namesToRead != null && namesToRead.contains(namesToRead));
627                if (read) {
628                    // Now read the property values ...
629                    Object[] values = deserializePropertyValues(stream, name, false, largeValues, skippedLargeValues, null);
630                    // Add the property to the collection ...
631                    Property property = propertyFactory.create(name, values);
632                    assert property != null;
633                    properties.add(property);
634                } else {
635                    // Skip the property ...
636                    deserializePropertyValues(stream, name, true, largeValues, skippedLargeValues, null);
637                }
638            }
639        }
640    
641        /**
642         * Deserialize the serialized property on the supplied object stream.
643         * 
644         * @param stream the stream that contains the serialized properties; may not be null
645         * @param largeValues the interface to use for writing large values; may not be null
646         * @return the deserialized property; never null
647         * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
648         * @throws ClassNotFoundException if the class for the value's object could not be found
649         * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
650         * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
651         */
652        public Property deserializeProperty( ObjectInputStream stream,
653                                             LargeValues largeValues ) throws IOException, ClassNotFoundException {
654            // Read the name ...
655            String nameStr = (String)stream.readObject();
656            Name name = valueFactories.getNameFactory().create(nameStr);
657            assert name != null;
658            // Now read the property values ...
659            Object[] values = deserializePropertyValues(stream, name, false, largeValues, largeValues, null);
660            // Add the property to the collection ...
661            return propertyFactory.create(name, values);
662        }
663    
664        /**
665         * Deserialize the serialized property on the supplied object stream.
666         * 
667         * @param stream the stream that contains the serialized properties; may not be null
668         * @param propertyName the name of the property being deserialized
669         * @param skip true if the values don't need to be read, or false if they are to be read
670         * @param largeValues the interface to use for writing large values; may not be null
671         * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
672         * @param references the interface to use for recording which {@link Reference} values were found (and/or removed) during
673         *        deserialization; may not be null
674         * @return the deserialized property values, or an empty list if there are no values
675         * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
676         * @throws ClassNotFoundException if the class for the value's object could not be found
677         * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
678         * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
679         */
680        public Object[] deserializePropertyValues( ObjectInputStream stream,
681                                                   Name propertyName,
682                                                   boolean skip,
683                                                   LargeValues largeValues,
684                                                   LargeValues skippedLargeValues,
685                                                   ReferenceValues references ) throws IOException, ClassNotFoundException {
686            assert stream != null;
687            assert propertyName != null;
688            assert largeValues != null;
689            assert skippedLargeValues != null;
690            // Read the number of values ...
691            int size = stream.readInt();
692            Object[] values = skip ? null : new Object[size];
693            for (int i = 0; i != size; ++i) {
694                Object value = null;
695                // Read the type of value ...
696                char type = stream.readChar();
697                switch (type) {
698                    case 'S':
699                        String stringValue = (String)stream.readObject();
700                        if (!skip) value = valueFactories.getStringFactory().create(stringValue);
701                        break;
702                    case 'b':
703                        boolean booleanValue = stream.readBoolean();
704                        if (!skip) value = valueFactories.getBooleanFactory().create(booleanValue);
705                        break;
706                    case 'i':
707                        int intValue = stream.readInt();
708                        if (!skip) value = valueFactories.getLongFactory().create(intValue);
709                        break;
710                    case 'l':
711                        long longValue = stream.readLong();
712                        if (!skip) value = valueFactories.getLongFactory().create(longValue);
713                        break;
714                    case 's':
715                        short shortValue = stream.readShort();
716                        if (!skip) value = valueFactories.getLongFactory().create(shortValue);
717                        break;
718                    case 'f':
719                        float floatValue = stream.readFloat();
720                        if (!skip) value = valueFactories.getDoubleFactory().create(floatValue);
721                        break;
722                    case 'd':
723                        double doubleValue = stream.readDouble();
724                        if (!skip) value = valueFactories.getDoubleFactory().create(doubleValue);
725                        break;
726                    case 'c':
727                        // char
728                        String charValue = "" + stream.readChar();
729                        if (!skip) value = valueFactories.getStringFactory().create(charValue);
730                        break;
731                    case 'U':
732                        // UUID
733                        long msb = stream.readLong();
734                        long lsb = stream.readLong();
735                        if (!skip) {
736                            UUID uuid = new UUID(msb, lsb);
737                            value = valueFactories.getUuidFactory().create(uuid);
738                        }
739                        break;
740                    case 'I':
741                        // URI
742                        String uriStr = (String)stream.readObject();
743                        if (!skip) value = valueFactories.getUriFactory().create(uriStr);
744                        break;
745                    case 'N':
746                        // Name
747                        String nameValueStr = (String)stream.readObject();
748                        if (!skip) value = valueFactories.getNameFactory().create(nameValueStr);
749                        break;
750                    case 'P':
751                        // Path
752                        String pathStr = (String)stream.readObject();
753                        if (!skip) value = valueFactories.getPathFactory().create(pathStr);
754                        break;
755                    case 'T':
756                        // DateTime
757                        String dateTimeStr = (String)stream.readObject();
758                        if (!skip) value = valueFactories.getDateFactory().create(dateTimeStr);
759                        break;
760                    case 'D':
761                        // BigDecimal
762                        Object bigDecimal = stream.readObject();
763                        if (!skip) value = valueFactories.getDecimalFactory().create(bigDecimal);
764                        break;
765                    case 'R':
766                        // Reference
767                        String refValue = (String)stream.readObject();
768                        Reference ref = valueFactories.getReferenceFactory().create(refValue);
769                        if (skip) {
770                            if (references != null) references.remove(ref);
771                        } else {
772                            value = ref;
773                            if (references != null) references.read(ref);
774                        }
775                        break;
776                    case 'B':
777                        // Binary
778                        // Read the length of the content ...
779                        long binaryLength = stream.readLong();
780                        byte[] content = new byte[(int)binaryLength];
781                        stream.read(content);
782                        if (!skip) {
783                            value = valueFactories.getBinaryFactory().create(content);
784                        }
785                        break;
786                    case 'L':
787                        // Large object ...
788                        // Read the hash ...
789                        int hashLength = stream.readInt();
790                        byte[] hash = new byte[hashLength];
791                        stream.read(hash);
792                        // Read the length of the content ...
793                        long length = stream.readLong();
794                        if (skip) {
795                            skippedLargeValues.read(valueFactories, hash, length);
796                        } else {
797                            BinaryFactory factory = valueFactories.getBinaryFactory();
798                            // Look for an already-loaded Binary value with the same hash ...
799                            value = factory.find(hash);
800                            if (value == null) {
801                                // Didn't find an existing large value, so we have to read the large value ...
802                                value = largeValues.read(valueFactories, hash, length);
803                            }
804                        }
805                        break;
806                    default:
807                        // All other objects ...
808                        Object object = stream.readObject();
809                        if (!skip) value = valueFactories.getObjectFactory().create(object);
810                        break;
811                }
812                if (value != null) values[i] = value;
813            }
814            return values;
815        }
816    
817        public byte[] computeHash( String value ) {
818            try {
819                return SecureHash.getHash(SecureHash.Algorithm.SHA_1, value.getBytes());
820            } catch (NoSuchAlgorithmException e) {
821                throw new SystemFailureException(e);
822            }
823        }
824    }