Package com.persistit

Source Code of com.persistit.DefaultObjectCoder

/**
* 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();
    }

}
TOP

Related Classes of com.persistit.DefaultObjectCoder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.