1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: SerialBinding.java,v 12.9 2008/02/07 17:12:25 mark Exp $
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 * @author Mark Hayes
41 */
42public class SerialBinding extends SerialBase implements EntryBinding {
43
44    private ClassCatalog classCatalog;
45    private Class baseClass;
46
47    /**
48     * Creates a serial binding.
49     *
50     * @param classCatalog is the catalog to hold shared class information and
51     * for a database should be a {@link StoredClassCatalog}.
52     *
53     * @param baseClass is the base class for serialized objects stored using
54     * this binding -- all objects using this binding must be an instance of
55     * this class.
56     */
57    public SerialBinding(ClassCatalog classCatalog, Class baseClass) {
58
59        if (classCatalog == null) {
60            throw new NullPointerException("classCatalog must be non-null");
61        }
62        this.classCatalog = classCatalog;
63        this.baseClass = baseClass;
64    }
65
66    /**
67     * Returns the base class for this binding.
68     *
69     * @return the base class for this binding.
70     */
71    public final Class getBaseClass() {
72
73        return baseClass;
74    }
75
76    /**
77     * Returns the class loader to be used during deserialization, or null if
78     * a default class loader should be used.  The default implementation of
79     * this method returns
80     * <code>Thread.currentThread().getContextClassLoader()</code> to use the
81     * context class loader for the current thread.
82     *
83     * <p>This method may be overridden to return a dynamically determined class
84     * loader.  For example, <code>getBaseClass().getClassLoader()</code> could
85     * be called to use the class loader for the base class, assuming that a
86     * base class has been specified.</p>
87     *
88     * <p>If this method returns null, a default class loader will be used as
89     * determined by the <code>java.io.ObjectInputStream.resolveClass</code>
90     * method.</p>
91     */
92    public ClassLoader getClassLoader() {
93
94        return Thread.currentThread().getContextClassLoader();
95    }
96
97    /**
98     * Deserialize an object from an entry buffer.  May only be called for data
99     * that was serialized using {@link #objectToEntry}, since the fixed
100     * serialization header is assumed to not be included in the input data.
101     * {@link SerialInput} is used to deserialize the object.
102     *
103     * @param entry is the input serialized entry.
104     *
105     * @return the output deserialized object.
106     */
107    public Object entryToObject(DatabaseEntry entry) {
108
109        int length = entry.getSize();
110        byte[] hdr = SerialOutput.getStreamHeader();
111        byte[] bufWithHeader = new byte[length + hdr.length];
112
113        System.arraycopy(hdr, 0, bufWithHeader, 0, hdr.length);
114        System.arraycopy(entry.getData(), entry.getOffset(),
115                         bufWithHeader, hdr.length, length);
116
117        try {
118            SerialInput jin = new SerialInput(
119                new FastInputStream(bufWithHeader, 0, bufWithHeader.length),
120                classCatalog,
121                getClassLoader());
122            return jin.readObject();
123        } catch (IOException e) {
124            throw new RuntimeExceptionWrapper(e);
125        } catch (ClassNotFoundException e) {
126            throw new RuntimeExceptionWrapper(e);
127        }
128    }
129
130    /**
131     * Serialize an object into an entry buffer.  The fixed serialization
132     * header is not included in the output data to save space, and therefore
133     * to deserialize the data the complementary {@link #entryToObject} method
134     * must be used.  {@link SerialOutput} is used to serialize the object.
135     *
136     * <p>Note that this method sets the DatabaseEntry offset property to a
137     * non-zero value and the size property to a value less than the length of
138     * the byte array.</p>
139     *
140     * @param object is the input deserialized object.
141     *
142     * @param entry is the output serialized entry.
143     *
144     * @throws IllegalArgumentException if the object is not an instance of the
145     * base class for this binding.
146     */
147    public void objectToEntry(Object object, DatabaseEntry entry) {
148
149        if (baseClass != null && !baseClass.isInstance(object)) {
150            throw new IllegalArgumentException(
151                        "Data object class (" + object.getClass() +
152                        ") not an instance of binding's base class (" +
153                        baseClass + ')');
154        }
155        FastOutputStream fo = getSerialOutput(object);
156        try {
157            SerialOutput jos = new SerialOutput(fo, classCatalog);
158            jos.writeObject(object);
159        } catch (IOException e) {
160            throw new RuntimeExceptionWrapper(e);
161        }
162
163        byte[] hdr = SerialOutput.getStreamHeader();
164        entry.setData(fo.getBufferBytes(), hdr.length,
165                     fo.getBufferLength() - hdr.length);
166    }
167}
168