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 }