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