/** * Copyright 2005-2012 Akiban Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.persistit; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.util.ArrayList; import java.util.HashMap; import com.persistit.encoding.CoderContext; import com.persistit.encoding.CoderManager; import com.persistit.encoding.KeyRenderer; import com.persistit.encoding.ObjectCoder; import com.persistit.encoding.ValueCoder; import com.persistit.exception.ConversionException; /** * <p> * An {@link ObjectCoder} that uses reflection to access the properties and/or * fields of an object. An <code>ObjectCoder</code> provides methods to encode * and decode properties or fields of an object into or from {@link Key Key}s * and {@link Value Value}s. * </p> * <p> * <code>ObjectCoder</code>s allow Persistit to store and retrieve arbitrary * objects - even non-Serializable objects - without byte-code enhancement, * without incurring the space or time overhead of Java serialization or the * need to modify the class to perform custom serialization. During * initialization, an application typically associates an * <code>ObjectCoder</code> with the <code>Class</code> of each object that will * be stored in or fetched from the Persistit database. An * <code>ObjectCoder</code> implements all of the logic necessary to encode and * decode the state of any object of that class to and from Persistit storage * structures. * </p> * <p> * A <code>DefaultObjectCoder</code> is a generic implementation of the * <code>ObjectCoder</code> interface. During initialization an application uses * the static {@link #registerObjectCoder registerObjectCoder} or * {@link #registerObjectCoderFromBean registerObjectCoderFromBean} method to * construct and register an <code>ObjectCoder</code> for a particular class * with the current {@link CoderManager}. To define a * <code>DefaultObjectCoder</code> for a class, all that is required is two * arrays, one containing names of properties or fields that will be used in * constructing <code>Key</code> values under which instances of the class will * be stored, and the other containing names of properties or fields that will * be encoded in the <code>Value</code> associated with that key. * </p> * <p> * Unlike {@link DefaultValueCoder}, which implements default serialization for * Persistit version 1.1, this extended implementation can encode and decode * non-serializable objects (that is, instances of classes that are do not * implement <code>java.io.Serializable</code>). However, classes handled by * this <code>ObjectCoder</code> must have a no-argument constructor which is * used to construct new objects instances in the * {@link ValueCoder#get(Value, Class, CoderContext)} method. An extension of * this class may override the {@link #newInstance()} method to provide * customized logic for constructing new instances of the client class. * </p> * <p> * <code>DefaultObjectCoder</code> may be used to serialize and deserialize the * private fields of an object through reflection. If the application using * Persistit is running in the context of a <code>SecurityManager</code>, the * permission <code>ReflectPermission("suppressAccessChecks")</code> is required * to permit this access. See the JDK documentation for * <code>java.lang.reflect.AccessibleObject</code> and <a * href="../../../../Object_Serialization_Notes.html"> Persistit JSA 1.1 Object * Serialization</a> for details. Similarly, the same permission is required * when deserializing an object with a private constructor. * </p> * <p> * For Java Runtime Environments 1.3 through 1.4.2, this class is unable to * deserialize fields marked <code>final</code> due to a bug in the JRE (see <a * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5044412"> bug * 5044412</a>). This bug was fixed in Java SE 5.0. * </p> * <p> * The following code fragment defines a simple class, registers a * <code>DefaultObjectCoder</code> for it, then provides methods for storing and * and fetching instances to and from a Persistit database: * * <pre> * <code> * * static class Vehicle * { * String id; * String description; * int speed; * int wheels; * int passengers * boolean canFly; * } * * static * { * DefaultObjectCoder.registerObjectCoder( * Vehicle.class, * {"id"}, * {"description", "speed", "wheels", "passengers", "canFly"}); * } * * void storeVehicle(Vehicle v, Exchange exchange) * throws PersistitException * { * exchange.getValue().put(v); * exchange.clear().append(v).store(); * } * * Vehicle getVehicle(String id, Exchange exchange) * throws PersistitException * { * Vehicle v = new Vehicle(); * v.id = id; * // * // Using the id field as primary key, fetch the remaining fields * // of the object. * // * exchange.clear().append(v).fetch().get(v); * return v; * } * </code> * </pre> * * </p> * * @author peter * @version 1.1 */ public class DefaultObjectCoder extends DefaultValueCoder implements KeyRenderer { private Builder _keyBuilder; /** * Map of keyName : Accessor[] */ private HashMap _secondaryKeyTupleMap; private ArrayList _secondaryKeyTupleList; private DefaultObjectCoder(final Persistit persistit, final Class clientClass, final Builder valueBuilder) { super(persistit, clientClass, valueBuilder); } /** * <p> * Convenience method that creates and registers a * <code>DefaultObjectCoder</code> for a Java Bean. * </p> * <p> * The supplied <code>Class</code> must conform to the requirements of a * Java bean; in particular it must have a no-argument constructor. The * resulting ObjectCoder will serialize and deserialize the properties of * this bean as determined by the BeanInfo derived from introspecting the * bean's class or its associated BeanInfo class. * </p> * <p> * The <code>keyPropertyNames</code> array specifies names of one or more * properties of the bean that are to be concatenated, in the order * specified by the array, to form the primary key under which instances of * this bean will be stored. * </p> * <p> * Persistit must be initialized at the time this method is called. This * method registers the newly created <code>DefaultObjectCoder</code> the * Persistit instance's current <code>CoderManager</code>. * </p> * * @param clientClass * The <code>Class</code> of object this * <code>DefaultObjectCoder</code> will encode and decode * * @param keyPropertyNames * Array of names of properties that constitute the primary key * of stored instances * * @return the newly registered <code>DefaultObjectCoder</code> * * @throws IntrospectionException */ public synchronized DefaultObjectCoder registerObjectCoderFromBean(final Persistit persistit, final Class clientClass, final String[] keyPropertyNames) throws IntrospectionException { final BeanInfo beanInfo = Introspector.getBeanInfo(clientClass); final PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); final boolean isKeyProperty[] = new boolean[descriptors.length]; for (int index = 0; index < keyPropertyNames.length; index++) { final String name = keyPropertyNames[index]; boolean found = false; for (int j = 0; !found && j < descriptors.length; j++) { if (descriptors[j].getName().equals(name)) { isKeyProperty[j] = true; found = true; } } if (!found) { throw new IllegalArgumentException("Bean for class " + clientClass.getName() + " has no property named " + name); } } int count = 0; final String[] valuePropertyNames = new String[descriptors.length - keyPropertyNames.length]; for (int j = 0; j < descriptors.length; j++) { if (!isKeyProperty[j]) { valuePropertyNames[count] = descriptors[j].getName(); count++; } } final Builder valueBuilder = new Builder("value", valuePropertyNames, clientClass); final DefaultObjectCoder coder = new DefaultObjectCoder(persistit, clientClass, valueBuilder); CoderManager cm = null; cm = persistit.getCoderManager(); cm.registerKeyCoder(clientClass, coder); cm.registerValueCoder(clientClass, coder); return coder; } /** * <p> * Convenience method that creates and registers a * <code>DefaultObjectCoder</code> for an arbitrary Java class. The class is * not required to be serializable (i.e., to implement * <code>java.io.Serializable</code>), but it must have a no-argument * constructor. If the class implements <code>Externalizable</code>, then * the constructor is required to be public, thus conforming to the contract * for <code>Externalizable</code> classes. Otherwise, the constructor may * be public, protected, package-private or private. * </p> * <p> * The supplied <code>Class</code> and the supplied names for fields or * properties constitute the state to be recorded in Persistit. The * resulting coder is capable of efficiently serializing and deserializing * instances of the client <code>Class</code> in Persistit records. * </p> * <p> * Two String arrays determine the structure of the stored data. Each * element in these arrays identifies the name of either a field or a * property of the client <code>Class</code>. For each name xyz, this * constructor first searches for a method with a signature compatible with * either * * <pre> * <code> * <i>Type</i>getXyz() * </code>or<code> boolean isXyz() * </code> * </pre> * * and a method with a signature compatible with * * <pre> * <code> * void setXyz(<i>Type</i> value) * </code> * </pre> * * In the case of the boolean property named <code>isXyz</code>, the value * <code><i>Type</i></code> must be <code>boolean</code>; otherwise, the * type of the value specified by the setter must be assignable from return * type of the getter. In other words, the setXyz method must accept any * value that the getXyz method might return. If multiple setXyz methods * meet this requirement, the method with the most specific argument type is * selected. * </p> * <p> * If both setXyz and either getXyz or isXyz methods meeting these criteria * are found then the accessor is a property accessor, and will be stored * and retrieved from the object using these methods. Otherwise, the * accessor name must be the name of an accessible field of the client * <code>Class</code>. Non-public fields are accessible only if permitted by * the security policy in which the code is executed and only on JDK * versions 1.2 and above. (See the JDK documentation for * <code>java.lang.reflect.AccessibleObject</code> for details.) * </p> * <p> * The <code>keyAccesssorNames</code> array identifies the properties or * fields whose values will constitute the primary key of an object stored * with this <code>objectCoder</code>; the <code>valueAccessorNames</code> * array identifies the properties or fields that will constitute the value * associated with that key. * </p> * <p> * Persistit must be initialized at the time this method is called. This * method registers the newly created <code>DefaultObjectCoder</code> the * Persistit instance's current <code>CoderManager</code>. * </p> * * @param clientClass * The <code>Class</code> whose instances are to be encoded and * decoded * * @param keyAccessorNames * Array of names of properties that constitute the primary key * of stored instances of the <code>clientClass</code>. * * @param valueAccessorNames * Array of names of properties that constitute the value of * stored instances of the <code>clientClass</code>. * * @return the newly registered <code>DefaultObjectCoder</code> * */ public synchronized static DefaultObjectCoder registerObjectCoder(final Persistit persistit, final Class clientClass, final String[] keyAccessorNames, final String[] valueAccessorNames) { final Builder keyBuilder = new Builder("primaryKey", keyAccessorNames, clientClass); final Builder valueBuilder = new Builder("value", valueAccessorNames, clientClass); final DefaultObjectCoder coder = new DefaultObjectCoder(persistit, clientClass, valueBuilder); coder._keyBuilder = keyBuilder; CoderManager cm = null; cm = persistit.getCoderManager(); cm.registerKeyCoder(clientClass, coder); cm.registerValueCoder(clientClass, coder); return coder; } /** * Construct a new instance of the client class. By default this uses a * no-argument constructor declared by the class, and is equivalent to * {@link Class#newInstance()}. Subclasses may override this method to * provide custom logic for constructing new instances. * * @return a new instance of the Class for which this * <code>DefaultObjectCoder</code> is registered. */ @Override protected Object newInstance() { return super.newInstance(); } /** * Construct and add a secondary index <code>Builder</code>. * * @param name * Name of the secondary index * @param keyAccessorNames * The property and/or field names * @return The newly constructed Builder */ public synchronized Builder addSecondaryIndexBuilder(final String name, final String[] keyAccessorNames) { final Builder builder = new Builder(name, keyAccessorNames, getClientClass()); if (_secondaryKeyTupleMap == null) { _secondaryKeyTupleMap = new HashMap(); _secondaryKeyTupleList = new ArrayList(); } final Builder oldBuilder = (Builder) _secondaryKeyTupleMap.put(name, builder); if (oldBuilder != null) _secondaryKeyTupleList.remove(oldBuilder); _secondaryKeyTupleList.add(builder); return builder; } /** * Remove a secondary index <code>Builder</code> specified by its name. * * @param name * Name if the secondary index to remove. * @return The Builder that was removed, or <code>null</code> if there was * none. */ public synchronized Builder removeSecondaryIndexBuilder(final String name) { if (_secondaryKeyTupleMap == null) return null; final Builder builder = (Builder) _secondaryKeyTupleMap.get(name); if (builder != null) { _secondaryKeyTupleList.remove(builder); _secondaryKeyTupleMap.remove(name); } return builder; } /** * Return a <code>Builder</code>s by index, according to the order in which * secondary index builders were added. * * @return The Builder */ public synchronized Builder getSecondaryIndexBuilder(final int index) { if (_secondaryKeyTupleList != null && index >= 0 && index < _secondaryKeyTupleList.size()) { return (Builder) _secondaryKeyTupleList.get(index); } throw new IndexOutOfBoundsException("No such secondary index: " + index); } /** * Return a <code>Builder</code> by name. * * @return The Builder, or <code>null</code> if there is no secondary index * with the specified name. */ public synchronized Builder getSecondaryIndexBuilder(final String name) { if (_secondaryKeyTupleMap == null) return null; return (Builder) _secondaryKeyTupleMap.get(name); } /** * Return the count of secondary index builders. * * @return The count */ public synchronized int getSecondaryIndexBuilderCount() { if (_secondaryKeyTupleList == null) return 0; else return _secondaryKeyTupleList.size(); } /** * Return the <code>Builder</code> that copies data values between a * <code>Key</code> and a client object. The resulting <code>Key</code> * value is intended to serve as the primary key for the object. * * @return The Builder */ public Builder getKeyBuilder() { return _keyBuilder; } @Override public void appendKeySegment(final Key key, final Object object, final CoderContext context) throws ConversionException { Accessor accessor = null; checkKeyAccessors(); final Builder keyBuilder = getKeyBuilder(context); try { final int count = keyBuilder.getSize(); for (int index = 0; index < count; index++) { accessor = keyBuilder.getAccessor(index); accessor.toKey(object, key); } } catch (final Exception e) { throw new ConversionException("Encoding " + accessor.toString() + " for " + getClientClass(), e); } } @Override public Object decodeKeySegment(final Key key, final Class clazz, final CoderContext context) throws ConversionException { if (clazz != getClientClass()) throw new ClassCastException("Client class " + getClientClass().getName() + " does not match requested class " + clazz.getName()); Object instance; try { instance = getClientClass().newInstance(); } catch (final Exception e) { throw new ConversionException("Unable to instantiate an instance of " + getClientClass(), e); } renderKeySegment(key, instance, clazz, context); return readResolve(instance); } @Override public boolean isZeroByteFree() { return false; } /** * <p> * Populates the state of the supplied target <code>Object</code> by * decoding the next key segment of the supplied <code>Key</code>. This * method will be called only if this <code>KeyRenderer</code> has been * registered with the current {@link CoderManager} to encode objects having * the supplied <code>Class</code> value. In addition, Persistit will never * call this method to decode a value that was <code>null</code> when * written because null values are handled by built-in encoding logic. * </p> * * @param key * The <code>Key</code> from which interior fields of the object * are to be retrieved * * @param target * An object into which the key segment is to be written * * @param clazz * The class of the object that was originally encoded into * Value. * * @param context * An arbitrary object that can optionally be supplied by the * application to convey an application-specific context for the * operation. (See {@link CoderContext}.) The default value is * <code>null</code>. * * @throws ConversionException */ @Override public void renderKeySegment(final Key key, final Object target, final Class clazz, final CoderContext context) throws ConversionException { if (clazz != getClientClass()) throw new ClassCastException("Client class " + getClientClass().getName() + " does not match requested class " + clazz.getName()); checkKeyAccessors(); final Builder keyBuilder = getKeyBuilder(context); final int count = keyBuilder.getSize(); Accessor accessor = null; try { for (int index = 0; index < count; index++) { accessor = _keyBuilder.getAccessor(index); accessor.fromKey(target, key); } } catch (final Exception e) { throw new ConversionException("Decoding " + accessor.toString() + " for " + getClientClass(), e); } } private Builder getKeyBuilder(final CoderContext context) { if (context == null || context == _keyBuilder) return _keyBuilder; if (_secondaryKeyTupleList != null) { final int count = getSecondaryIndexBuilderCount(); for (int index = 0; index < count; index++) { if (context == _secondaryKeyTupleList.get(index)) { return (Builder) context; } } } throw new ConversionException("No such Builder " + context); } private void checkKeyAccessors() { if (_keyBuilder.getSize() == 0) { throw new ConversionException("ObjectCoder for class " + getClientClass().getName() + " has no Key fields or properties"); } } /** * Return a String description of this DefaultObjectCoder. The String * includes the client class name, and the property or field names * identifying the properties and/or fields this coder accesses and * modifies. * * @return A String description. */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("DefaultObjectCoder("); sb.append(getClientClass().getName()); sb.append(","); sb.append(_keyBuilder.toString()); sb.append(","); sb.append(getValueBuilder().toString()); if (_secondaryKeyTupleList != null) { for (int index = 0; index < _secondaryKeyTupleList.size(); index++) { sb.append(","); sb.append(_secondaryKeyTupleList.get(index)); } } sb.append(")"); return sb.toString(); } }