1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2000-2009 Oracle. All rights reserved. 5 * 6 * $Id$ 7 */ 8 9package com.sleepycat.bind.serial; 10 11import java.io.IOException; 12 13import com.sleepycat.bind.EntryBinding; 14import com.sleepycat.db.DatabaseEntry; 15import com.sleepycat.util.FastInputStream; 16import com.sleepycat.util.FastOutputStream; 17import com.sleepycat.util.RuntimeExceptionWrapper; 18 19/** 20 * A concrete <code>EntryBinding</code> that treats a key or data entry as 21 * a serialized object. 22 * 23 * <p>This binding stores objects in serialized object format. The 24 * deserialized objects are returned by the binding, and their 25 * <code>Class</code> must implement the <code>Serializable</code> 26 * interface.</p> 27 * 28 * <p>For key bindings, a tuple binding is usually a better choice than a 29 * serial binding. A tuple binding gives a reasonable sort order, and works 30 * with comparators in all cases -- see below.</p> 31 * 32 * <p><em>WARNING:</em> SerialBinding should not be used with Berkeley DB Java 33 * Edition for key bindings, when a custom comparator is used. In JE, 34 * comparators are instantiated and called internally at times when databases 35 * are not accessible. Because serial bindings depend on the class catalog 36 * database, a serial binding cannot be used during these times. An attempt 37 * to use a serial binding with a custom comparator will result in a 38 * NullPointerException during environment open or close.</p> 39 * 40 * <p><a name="evolution"><strong>Class Evolution</strong></a></p> 41 * 42 * <p>{@code SerialBinding} and other classes in this package use standard Java 43 * serialization and all rules of Java serialization apply. This includes the 44 * rules for class evolution. Once an instance of a class is stored, the class 45 * must maintain its {@code serialVersionUID} and follow the rules defined in 46 * the Java specification. To use a new incompatible version of a class, a 47 * different {@link ClassCatalog} must be used or the class catalog database 48 * must be truncated.</p> 49 * 50 * <p>If more advanced class evolution features are required, consider using 51 * the {@link com.sleepycat.persist.evolve Direct Persistence Layer}.</p> 52 * 53 * @author Mark Hayes 54 */ 55public class SerialBinding<E> extends SerialBase implements EntryBinding<E> { 56 57 private ClassCatalog classCatalog; 58 private Class<E> baseClass; 59 60 /** 61 * Creates a serial binding. 62 * 63 * @param classCatalog is the catalog to hold shared class information and 64 * for a database should be a {@link StoredClassCatalog}. 65 * 66 * @param baseClass is the base class for serialized objects stored using 67 * this binding -- all objects using this binding must be an instance of 68 * this class. 69 */ 70 public SerialBinding(ClassCatalog classCatalog, Class<E> baseClass) { 71 72 if (classCatalog == null) { 73 throw new NullPointerException("classCatalog must be non-null"); 74 } 75 this.classCatalog = classCatalog; 76 this.baseClass = baseClass; 77 } 78 79 /** 80 * Returns the base class for this binding. 81 * 82 * @return the base class for this binding. 83 */ 84 public final Class<E> getBaseClass() { 85 86 return baseClass; 87 } 88 89 /** 90 * Returns the class loader to be used during deserialization, or null if 91 * a default class loader should be used. The default implementation of 92 * this method returns 93 * <code>Thread.currentThread().getContextClassLoader()</code> to use the 94 * context class loader for the current thread. 95 * 96 * <p>This method may be overridden to return a dynamically determined class 97 * loader. For example, <code>getBaseClass().getClassLoader()</code> could 98 * be called to use the class loader for the base class, assuming that a 99 * base class has been specified.</p> 100 * 101 * <p>If this method returns null, a default class loader will be used as 102 * determined by the <code>java.io.ObjectInputStream.resolveClass</code> 103 * method.</p> 104 */ 105 public ClassLoader getClassLoader() { 106 107 return Thread.currentThread().getContextClassLoader(); 108 } 109 110 /** 111 * Deserialize an object from an entry buffer. May only be called for data 112 * that was serialized using {@link #objectToEntry}, since the fixed 113 * serialization header is assumed to not be included in the input data. 114 * {@link SerialInput} is used to deserialize the object. 115 * 116 * @param entry is the input serialized entry. 117 * 118 * @return the output deserialized object. 119 */ 120 public E entryToObject(DatabaseEntry entry) { 121 122 int length = entry.getSize(); 123 byte[] hdr = SerialOutput.getStreamHeader(); 124 byte[] bufWithHeader = new byte[length + hdr.length]; 125 126 System.arraycopy(hdr, 0, bufWithHeader, 0, hdr.length); 127 System.arraycopy(entry.getData(), entry.getOffset(), 128 bufWithHeader, hdr.length, length); 129 130 try { 131 SerialInput jin = new SerialInput( 132 new FastInputStream(bufWithHeader, 0, bufWithHeader.length), 133 classCatalog, 134 getClassLoader()); 135 return (E) jin.readObject(); 136 } catch (IOException e) { 137 throw new RuntimeExceptionWrapper(e); 138 } catch (ClassNotFoundException e) { 139 throw new RuntimeExceptionWrapper(e); 140 } 141 } 142 143 /** 144 * Serialize an object into an entry buffer. The fixed serialization 145 * header is not included in the output data to save space, and therefore 146 * to deserialize the data the complementary {@link #entryToObject} method 147 * must be used. {@link SerialOutput} is used to serialize the object. 148 * 149 * <p>Note that this method sets the DatabaseEntry offset property to a 150 * non-zero value and the size property to a value less than the length of 151 * the byte array.</p> 152 * 153 * @param object is the input deserialized object. 154 * 155 * @param entry is the output serialized entry. 156 * 157 * @throws IllegalArgumentException if the object is not an instance of the 158 * base class for this binding. 159 */ 160 public void objectToEntry(E object, DatabaseEntry entry) { 161 162 if (baseClass != null && !baseClass.isInstance(object)) { 163 throw new IllegalArgumentException( 164 "Data object class (" + object.getClass() + 165 ") not an instance of binding's base class (" + 166 baseClass + ')'); 167 } 168 FastOutputStream fo = getSerialOutput(object); 169 try { 170 SerialOutput jos = new SerialOutput(fo, classCatalog); 171 jos.writeObject(object); 172 } catch (IOException e) { 173 throw new RuntimeExceptionWrapper(e); 174 } 175 176 byte[] hdr = SerialOutput.getStreamHeader(); 177 entry.setData(fo.getBufferBytes(), hdr.length, 178 fo.getBufferLength() - hdr.length); 179 } 180} 181