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.simple.LargeValueEntity;
42 import org.modeshape.graph.ExecutionContext;
43 import org.modeshape.graph.ModeShapeLexicon;
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.ReferenceFactory;
54 import org.modeshape.graph.property.UuidFactory;
55 import org.modeshape.graph.property.ValueFactories;
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 Reference ref = (Reference)value;
296 if (ref.isWeak()) {
297 stream.writeChar('W');
298 } else {
299 stream.writeChar('R');
300 }
301 stream.writeObject(ref.getString());
302 references.write(ref);
303 } else if (value instanceof Binary) {
304 Binary binary = (Binary)value;
305 byte[] hash = null;
306 long length = 0;
307 try {
308 binary.acquire();
309 length = binary.getSize();
310 if (largeValues != null && length > largeValues.getMinimumSize()) {
311 // Store the value in the large values area, but record the hash and length here.
312 hash = binary.getHash();
313 stream.writeChar('L');
314 stream.writeInt(hash.length);
315 stream.write(hash);
316 stream.writeLong(length);
317 // Write to large objects after releasing the binary
318 } else {
319 // The value is small enough to store here ...
320 stream.writeChar('B');
321 stream.writeLong(length);
322 InputStream data = binary.getStream();
323 try {
324 byte[] buffer = new byte[1024];
325 int numRead = 0;
326 while ((numRead = data.read(buffer)) > -1) {
327 stream.write(buffer, 0, numRead);
328 }
329 } finally {
330 data.close();
331 }
332 }
333 } finally {
334 binary.release();
335 }
336 // If this is a large value and the binary has been released, write it to the large objects ...
337 if (largeValues != null && hash != null) {
338 largeValues.write(hash, length, PropertyType.BINARY, value);
339 }
340 } else {
341 // Other kinds of values ...
342 stream.writeChar('O');
343 stream.writeObject(value);
344 }
345 }
346 stream.flush();
347 return true;
348 }
349
350 /**
351 * Deserialize the existing properties from the supplied input stream, update the properties, and then serialize the updated
352 * properties to the output stream.
353 *
354 * @param input the stream from which the existing properties are to be deserialized; may not be null
355 * @param output the stream to which the updated properties are to be serialized; may not be null
356 * @param updatedProperties the properties that are being updated (or removed, if there are no values); may not be null
357 * @param largeValues the interface to use for writing large values; may not be null
358 * @param removedLargeValues the interface to use for recording the large values that were removed; may not be null
359 * @param createdProperties the set into which should be placed the names of the properties that were created; may not be null
360 * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
361 * null if the references do not need to be accumulated
362 * @return the number of properties
363 * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
364 * @throws ClassNotFoundException if the class for the value's object could not be found
365 */
366 public int reserializeProperties( ObjectInputStream input,
367 ObjectOutputStream output,
368 Map<Name, Property> updatedProperties,
369 LargeValues largeValues,
370 LargeValues removedLargeValues,
371 Set<Name> createdProperties,
372 ReferenceValues references ) throws IOException, ClassNotFoundException {
373 assert input != null;
374 assert output != null;
375 assert updatedProperties != null;
376 assert createdProperties != null;
377 assert largeValues != null;
378 assert references != null;
379 // Assemble a set of property names to skip deserializing
380 Map<Name, Property> allProperties = new HashMap<Name, Property>();
381
382 // Start out by assuming that all properties are new ...
383 createdProperties.addAll(updatedProperties.keySet());
384
385 // Read the number of properties ...
386 int count = input.readInt();
387 // Deserialize all of the properties ...
388 for (int i = 0; i != count; ++i) {
389 // Read the property name ...
390 String nameStr = (String)input.readObject();
391 Name name = valueFactories.getNameFactory().create(nameStr);
392 assert name != null;
393 if (updatedProperties.containsKey(name)) {
394 // Deserialized, but don't materialize ...
395 deserializePropertyValues(input, name, true, largeValues, removedLargeValues, references);
396 } else {
397 // Now read the property values ...
398 Object[] values = deserializePropertyValues(input, name, false, largeValues, removedLargeValues, references);
399 // Add the property to the collection ...
400 Property property = propertyFactory.create(name, values);
401 assert property != null;
402 allProperties.put(name, property);
403 }
404 // This is an existing property, so remove it from the set of created properties ...
405 createdProperties.remove(name);
406 }
407
408 // Add all the updated properties ...
409 for (Map.Entry<Name, Property> entry : updatedProperties.entrySet()) {
410 Property updated = entry.getValue();
411 Name name = entry.getKey();
412 if (updated == null) {
413 allProperties.remove(name);
414 } else {
415 allProperties.put(name, updated);
416 }
417 }
418
419 // Serialize properties ...
420 int numProperties = allProperties.size();
421 output.writeInt(numProperties);
422 for (Property property : allProperties.values()) {
423 if (property == null) continue;
424 serializeProperty(output, property, largeValues, references);
425 }
426 return numProperties;
427 }
428
429 /**
430 * Deserialize the properties, adjust all {@link Reference} values that point to an "old" UUID to point to the corresponding
431 * "new" UUID, and reserialize the properties. If any reference is to a UUID not in the map, it is left untouched.
432 * <p>
433 * This is an efficient method that (for the most part) reads from the input stream and directly writes to the output stream.
434 * The exception is when a Reference value is read, that Reference is attempted to be remapped to a new Reference and written
435 * 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
436 * written directly.)
437 * </p>
438 *
439 * @param input the stream from which the existing properties are to be deserialized; may not be null
440 * @param output the stream to which the updated properties are to be serialized; may not be null
441 * @param oldUuidToNewUuid the map of old-to-new UUIDs; may not be null
442 * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
443 * @throws ClassNotFoundException if the class for the value's object could not be found
444 */
445 public void adjustReferenceProperties( ObjectInputStream input,
446 ObjectOutputStream output,
447 Map<String, String> oldUuidToNewUuid ) throws IOException, ClassNotFoundException {
448 assert input != null;
449 assert output != null;
450 assert oldUuidToNewUuid != null;
451
452 UuidFactory uuidFactory = valueFactories.getUuidFactory();
453
454 // Read the number of properties ...
455 int count = input.readInt();
456 output.writeInt(count);
457 // Deserialize all of the proeprties ...
458 for (int i = 0; i != count; ++i) {
459 // Read and write the property name ...
460 Object name = input.readObject();
461 output.writeObject(name);
462 // Read and write the number of values ...
463 int numValues = input.readInt();
464 output.writeInt(numValues);
465 // Now read and write each property value ...
466 for (int j = 0; j != numValues; ++j) {
467 // Read and write the type of value ...
468 char type = input.readChar();
469 output.writeChar(type);
470 switch (type) {
471 case 'S':
472 output.writeObject(input.readObject());
473 break;
474 case 'b':
475 output.writeBoolean(input.readBoolean());
476 break;
477 case 'i':
478 output.writeInt(input.readInt());
479 break;
480 case 'l':
481 output.writeLong(input.readLong());
482 break;
483 case 's':
484 output.writeShort(input.readShort());
485 break;
486 case 'f':
487 output.writeFloat(input.readFloat());
488 break;
489 case 'd':
490 output.writeDouble(input.readDouble());
491 break;
492 case 'c':
493 // char
494 output.writeChar(input.readChar());
495 break;
496 case 'U':
497 // UUID
498 output.writeLong(input.readLong());
499 output.writeLong(input.readLong());
500 break;
501 case 'I':
502 // URI
503 output.writeObject(input.readObject());
504 break;
505 case 'N':
506 // Name
507 output.writeObject(input.readObject());
508 break;
509 case 'P':
510 // Path
511 output.writeObject(input.readObject());
512 break;
513 case 'T':
514 // DateTime
515 output.writeObject(input.readObject());
516 break;
517 case 'D':
518 // BigDecimal
519 output.writeObject(input.readObject());
520 break;
521 case 'R':
522 // Reference
523 String refValue = (String)input.readObject();
524 ReferenceFactory referenceFactory = valueFactories.getReferenceFactory();
525 Reference ref = referenceFactory.create(refValue);
526 try {
527 UUID toUuid = uuidFactory.create(ref);
528 String newUuid = oldUuidToNewUuid.get(toUuid.toString());
529 if (newUuid != null) {
530 // Create a new reference ...
531 ref = referenceFactory.create(newUuid);
532 refValue = ref.getString();
533 }
534 } catch (ValueFormatException e) {
535 // Unknown reference, so simply write it again ...
536 }
537 // Write the reference ...
538 output.writeObject(refValue);
539 break;
540 case 'W':
541 // Weak Reference
542 refValue = (String)input.readObject();
543 referenceFactory = valueFactories.getWeakReferenceFactory();
544 ref = referenceFactory.create(refValue);
545 try {
546 UUID toUuid = uuidFactory.create(ref);
547 String newUuid = oldUuidToNewUuid.get(toUuid.toString());
548 if (newUuid != null) {
549 // Create a new reference ...
550 ref = referenceFactory.create(newUuid);
551 refValue = ref.getString();
552 }
553 } catch (ValueFormatException e) {
554 // Unknown reference, so simply write it again ...
555 }
556 // Write the reference ...
557 output.writeObject(refValue);
558 break;
559 case 'B':
560 // Binary
561 // Read the length of the content ...
562 long binaryLength = input.readLong();
563 byte[] content = new byte[(int)binaryLength];
564 input.read(content);
565 // Now write out the value ...
566 output.writeLong(binaryLength);
567 output.write(content);
568 break;
569 case 'L':
570 // Large object ...
571 int hashLength = input.readInt();
572 byte[] hash = new byte[hashLength];
573 input.read(hash);
574 long length = input.readLong();
575 // write to the output ...
576 output.writeInt(hash.length);
577 output.write(hash);
578 output.writeLong(length);
579 break;
580 default:
581 // All other objects ...
582 output.writeObject(input.readObject());
583 break;
584 }
585 }
586 }
587 }
588
589 /**
590 * Deserialize the serialized properties on the supplied object stream.
591 *
592 * @param stream the stream that contains the serialized properties; may not be null
593 * @param properties the collection into which each deserialized property is to be placed; may not be null
594 * @param largeValues the interface to use for writing large values; may not be null
595 * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
596 * @throws ClassNotFoundException if the class for the value's object could not be found
597 * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
598 * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
599 */
600 public void deserializeAllProperties( ObjectInputStream stream,
601 Collection<Property> properties,
602 LargeValues largeValues ) throws IOException, ClassNotFoundException {
603 assert stream != null;
604 assert properties != null;
605 // Read the number of properties ...
606 int count = stream.readInt();
607 for (int i = 0; i != count; ++i) {
608 Property property = deserializeProperty(stream, largeValues);
609 assert property != null;
610 properties.add(property);
611 }
612 }
613
614 /**
615 * Deserialize the serialized properties on the supplied object stream.
616 *
617 * @param stream the stream that contains the serialized properties; may not be null
618 * @param properties the collection into which each deserialized property is to be placed; may not be null
619 * @param names the names of the properties that should be deserialized; should not be null or empty
620 * @param largeValues the interface to use for writing large values; may not be null
621 * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
622 * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
623 * @throws ClassNotFoundException if the class for the value's object could not be found
624 * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
625 * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
626 */
627 public void deserializeSomeProperties( ObjectInputStream stream,
628 Collection<Property> properties,
629 LargeValues largeValues,
630 LargeValues skippedLargeValues,
631 Name... names ) throws IOException, ClassNotFoundException {
632 assert stream != null;
633 assert properties != null;
634 assert names != null;
635 assert names.length > 0;
636 Name nameToRead = null;
637 Set<Name> namesToRead = null;
638 if (names.length == 1) {
639 nameToRead = names[0];
640 } else {
641 namesToRead = new HashSet<Name>();
642 for (Name name : names) {
643 if (name != null) namesToRead.add(name);
644 }
645 }
646
647 // Read the number of properties ...
648 boolean read = false;
649 int count = stream.readInt();
650
651 // Now, read the properties (or skip the ones that we're not supposed to read) ...
652 for (int i = 0; i != count; ++i) {
653 // Read the name ...
654 String nameStr = (String)stream.readObject();
655 Name name = valueFactories.getNameFactory().create(nameStr);
656 assert name != null;
657 read = name.equals(nameToRead) || (namesToRead != null && namesToRead.contains(namesToRead));
658 if (read) {
659 // Now read the property values ...
660 Object[] values = deserializePropertyValues(stream, name, false, largeValues, skippedLargeValues, null);
661 // Add the property to the collection ...
662 Property property = propertyFactory.create(name, values);
663 assert property != null;
664 properties.add(property);
665 } else {
666 // Skip the property ...
667 deserializePropertyValues(stream, name, true, largeValues, skippedLargeValues, null);
668 }
669 }
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 largeValues the interface to use for writing large values; may not be null
677 * @return the deserialized property; never null
678 * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
679 * @throws ClassNotFoundException if the class for the value's object could not be found
680 * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
681 * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
682 */
683 public Property deserializeProperty( ObjectInputStream stream,
684 LargeValues largeValues ) throws IOException, ClassNotFoundException {
685 // Read the name ...
686 String nameStr = (String)stream.readObject();
687 Name name = valueFactories.getNameFactory().create(nameStr);
688 assert name != null;
689 // Now read the property values ...
690 Object[] values = deserializePropertyValues(stream, name, false, largeValues, largeValues, null);
691 // Add the property to the collection ...
692 return propertyFactory.create(name, values);
693 }
694
695 /**
696 * Deserialize the serialized property on the supplied object stream.
697 *
698 * @param stream the stream that contains the serialized properties; may not be null
699 * @param propertyName the name of the property being deserialized
700 * @param skip true if the values don't need to be read, or false if they are to be read
701 * @param largeValues the interface to use for writing large values; may not be null
702 * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
703 * @param references the interface to use for recording which {@link Reference} values were found (and/or removed) during
704 * deserialization; may not be null
705 * @return the deserialized property values, or an empty list if there are no values
706 * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
707 * @throws ClassNotFoundException if the class for the value's object could not be found
708 * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
709 * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
710 */
711 public Object[] deserializePropertyValues( ObjectInputStream stream,
712 Name propertyName,
713 boolean skip,
714 LargeValues largeValues,
715 LargeValues skippedLargeValues,
716 ReferenceValues references ) throws IOException, ClassNotFoundException {
717 assert stream != null;
718 assert propertyName != null;
719 assert largeValues != null;
720 assert skippedLargeValues != null;
721 // Read the number of values ...
722 int size = stream.readInt();
723 Object[] values = skip ? null : new Object[size];
724 for (int i = 0; i != size; ++i) {
725 Object value = null;
726 // Read the type of value ...
727 char type = stream.readChar();
728 switch (type) {
729 case 'S':
730 String stringValue = (String)stream.readObject();
731 if (!skip) value = valueFactories.getStringFactory().create(stringValue);
732 break;
733 case 'b':
734 boolean booleanValue = stream.readBoolean();
735 if (!skip) value = valueFactories.getBooleanFactory().create(booleanValue);
736 break;
737 case 'i':
738 int intValue = stream.readInt();
739 if (!skip) value = valueFactories.getLongFactory().create(intValue);
740 break;
741 case 'l':
742 long longValue = stream.readLong();
743 if (!skip) value = valueFactories.getLongFactory().create(longValue);
744 break;
745 case 's':
746 short shortValue = stream.readShort();
747 if (!skip) value = valueFactories.getLongFactory().create(shortValue);
748 break;
749 case 'f':
750 float floatValue = stream.readFloat();
751 if (!skip) value = valueFactories.getDoubleFactory().create(floatValue);
752 break;
753 case 'd':
754 double doubleValue = stream.readDouble();
755 if (!skip) value = valueFactories.getDoubleFactory().create(doubleValue);
756 break;
757 case 'c':
758 // char
759 String charValue = "" + stream.readChar();
760 if (!skip) value = valueFactories.getStringFactory().create(charValue);
761 break;
762 case 'U':
763 // UUID
764 long msb = stream.readLong();
765 long lsb = stream.readLong();
766 if (!skip) {
767 UUID uuid = new UUID(msb, lsb);
768 value = valueFactories.getUuidFactory().create(uuid);
769 }
770 break;
771 case 'I':
772 // URI
773 String uriStr = (String)stream.readObject();
774 if (!skip) value = valueFactories.getUriFactory().create(uriStr);
775 break;
776 case 'N':
777 // Name
778 String nameValueStr = (String)stream.readObject();
779 if (!skip) value = valueFactories.getNameFactory().create(nameValueStr);
780 break;
781 case 'P':
782 // Path
783 String pathStr = (String)stream.readObject();
784 if (!skip) value = valueFactories.getPathFactory().create(pathStr);
785 break;
786 case 'T':
787 // DateTime
788 String dateTimeStr = (String)stream.readObject();
789 if (!skip) value = valueFactories.getDateFactory().create(dateTimeStr);
790 break;
791 case 'D':
792 // BigDecimal
793 Object bigDecimal = stream.readObject();
794 if (!skip) value = valueFactories.getDecimalFactory().create(bigDecimal);
795 break;
796 case 'R':
797 // Reference
798 String refValue = (String)stream.readObject();
799 Reference ref = valueFactories.getReferenceFactory().create(refValue);
800 if (skip) {
801 if (references != null) references.remove(ref);
802 } else {
803 value = ref;
804 if (references != null) references.read(ref);
805 }
806 break;
807 case 'W':
808 // Weak reference
809 refValue = (String)stream.readObject();
810 ref = valueFactories.getWeakReferenceFactory().create(refValue);
811 if (skip) {
812 if (references != null) references.remove(ref);
813 } else {
814 value = ref;
815 if (references != null) references.read(ref);
816 }
817 break;
818 case 'B':
819 // Binary
820 // Read the length of the content ...
821 long binaryLength = stream.readLong();
822 byte[] content = new byte[(int)binaryLength];
823 stream.readFully(content, 0, content.length);
824 if (!skip) {
825 value = valueFactories.getBinaryFactory().create(content);
826 }
827 break;
828 case 'L':
829 // Large object ...
830 // Read the hash ...
831 int hashLength = stream.readInt();
832 byte[] hash = new byte[hashLength];
833 stream.readFully(hash, 0, hashLength);
834 // Read the length of the content ...
835 long length = stream.readLong();
836 if (skip) {
837 skippedLargeValues.read(valueFactories, hash, length);
838 } else {
839 BinaryFactory factory = valueFactories.getBinaryFactory();
840 // Look for an already-loaded Binary value with the same hash ...
841 value = factory.find(hash);
842 if (value == null) {
843 // Didn't find an existing large value, so we have to read the large value ...
844 value = largeValues.read(valueFactories, hash, length);
845 }
846 }
847 break;
848 default:
849 // All other objects ...
850 Object object = stream.readObject();
851 if (!skip) value = valueFactories.getObjectFactory().create(object);
852 break;
853 }
854 if (value != null) values[i] = value;
855 }
856 return values;
857 }
858
859 public byte[] computeHash( String value ) {
860 try {
861 return SecureHash.getHash(SecureHash.Algorithm.SHA_1, value.getBytes());
862 } catch (NoSuchAlgorithmException e) {
863 throw new SystemFailureException(e);
864 }
865 }
866 }