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