views:

506

answers:

2

The framework I am developing for my application relies very heavily on dynamically generated domain objects. I recently started using Spring WebFlow and now need to be able to serialize my domain objects that will be kept in flow scope.

I have done a bit of research and figured out that I can use writeReplace() and readResolve(). The only catch is that I need to look-up a factory in the Spring context. I tried to use @Configurable(preConstruction = true) in conjunction with the BeanFactoryAware marker interface.

But beanFactory is always null when I try to use it in my createEntity() method. Neither the default constructor nor the setBeanFactory() injector are called.

Has anybody tried this or something similar? I have included relevant class below.

Thanks in advance, Brian

/*
 *  Copyright 2008 Brian Thomas Matthews Limited.
 *  All rights reserved, worldwide.
 *
 *  This software and all information contained herein is the property of
 *  Brian Thomas Matthews Limited. Any dissemination, disclosure, use, or
 *  reproduction of this material for any reason inconsistent with the
 *  express purpose for which it has been disclosed is strictly forbidden.
 */

package com.btmatthews.dmf.domain.impl.cglib;

import java.io.InvalidObjectException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.util.StringUtils;

import com.btmatthews.dmf.domain.IEntity;
import com.btmatthews.dmf.domain.IEntityFactory;
import com.btmatthews.dmf.domain.IEntityID;
import com.btmatthews.dmf.spring.IEntityDefinitionBean;

/**
 * This class represents the serialized form of a domain object implemented
 * using CGLib. The readResolve() method recreates the actual domain object
 * after it has been deserialized into Serializable. You must define
 * <spring-configured/> in the application context.
 * 
 * @param <S>
 *            The interface that defines the properties of the base domain
 *            object.
 * @param <T>
 *            The interface that defines the properties of the derived domain
 *            object.
 * @author <a href="mailto:[email protected]">Brian Matthews</a>
 * @version 1.0
 */
@Configurable(preConstruction = true)
public final class SerializedCGLibEntity<S extends IEntity<S>, T extends S>
    implements Serializable, BeanFactoryAware
{
    /**
     * Used for logging.
     */
    private static final Logger LOG = LoggerFactory
        .getLogger(SerializedCGLibEntity.class);

    /**
     * The serialization version number.
     */
    private static final long serialVersionUID = 3830830321957878319L;

    /**
     * The application context. Note this is not serialized.
     */
    private transient BeanFactory beanFactory;

    /**
     * The domain object name.
     */
    private String entityName;

    /**
     * The domain object identifier.
     */
    private IEntityID<S> entityId;

    /**
     * The domain object version number.
     */
    private long entityVersion;

    /**
     * The attributes of the domain object.
     */
    private HashMap<?, ?> entityAttributes;

    /**
     * The default constructor.
     */
    public SerializedCGLibEntity()
    {
        SerializedCGLibEntity.LOG
            .debug("Initializing with default constructor");
    }

    /**
     * Initialise with the attributes to be serialised.
     * 
     * @param name
     *            The entity name.
     * @param id
     *            The domain object identifier.
     * @param version
     *            The entity version.
     * @param attributes
     *            The entity attributes.
     */
    public SerializedCGLibEntity(final String name, final IEntityID<S> id,
        final long version, final HashMap<?, ?> attributes)
    {
        SerializedCGLibEntity.LOG
            .debug("Initializing with parameterized constructor");

        this.entityName = name;
        this.entityId = id;
        this.entityVersion = version;
        this.entityAttributes = attributes;
    }

    /**
     * Inject the bean factory.
     * 
     * @param factory
     *            The bean factory.
     */
    public void setBeanFactory(final BeanFactory factory)
    {
        SerializedCGLibEntity.LOG.debug("Injected bean factory");

        this.beanFactory = factory;
    }

    /**
     * Called after deserialisation. The corresponding entity factory is
     * retrieved from the bean application context and BeanUtils methods are
     * used to initialise the object.
     * 
     * @return The initialised domain object.
     * @throws ObjectStreamException
     *             If there was a problem creating or initialising the domain
     *             object.
     */
    public Object readResolve()
        throws ObjectStreamException
    {
        SerializedCGLibEntity.LOG.debug("Transforming deserialized object");

        final T entity = this.createEntity();
        entity.setId(this.entityId);
        try
        {
            PropertyUtils.setSimpleProperty(entity, "version",
            this.entityVersion);
            for (Map.Entry<?, ?> entry : this.entityAttributes.entrySet())
            {
                PropertyUtils.setSimpleProperty(entity, entry.getKey()
                    .toString(), entry.getValue());
            }
        }
        catch (IllegalAccessException e)
        {
            throw new InvalidObjectException(e.getMessage());
        }
        catch (InvocationTargetException e)
        {
            throw new InvalidObjectException(e.getMessage());
        }
        catch (NoSuchMethodException e)
        {
            throw new InvalidObjectException(e.getMessage());
        }
        return entity;
    }

    /**
     * Lookup the entity factory in the application context and create an
     * instance of the entity. The entity factory is located by getting the
     * entity definition bean and using the factory registered with it or
     * getting the entity factory. The name used for the definition bean lookup
     * is ${entityName}Definition while ${entityName} is used for the factory
     * lookup.
     * 
     * @return The domain object instance.
     * @throws ObjectStreamException
     *             If the entity definition bean or entity factory were not
     *             available.
     */
    @SuppressWarnings("unchecked")
    private T createEntity()
        throws ObjectStreamException
    {
        SerializedCGLibEntity.LOG.debug("Getting domain object factory");

        // Try to use the entity definition bean

        final IEntityDefinitionBean<S, T> entityDefinition = (IEntityDefinitionBean<S, T>)this.beanFactory
            .getBean(StringUtils.uncapitalize(this.entityName) + "Definition",
                IEntityDefinitionBean.class);

        if (entityDefinition != null)
        {
            final IEntityFactory<S, T> entityFactory = entityDefinition
                .getFactory();
            if (entityFactory != null)
            {
                 SerializedCGLibEntity.LOG
                    .debug("Domain object factory obtained via enity definition bean");

                 return entityFactory.create();
            }
        }

        // Try to use the entity factory

        final IEntityFactory<S, T> entityFactory = (IEntityFactory<S, T>)this.beanFactory
            .getBean(StringUtils.uncapitalize(this.entityName) + "Factory",
                IEntityFactory.class);

        if (entityFactory != null)
        {
            SerializedCGLibEntity.LOG
                .debug("Domain object factory obtained via direct look-up");

            return entityFactory.create();
        }

        // Neither worked!

        SerializedCGLibEntity.LOG.warn("Cannot find domain object factory");

        throw new InvalidObjectException(
            "No entity definition or factory found for " + this.entityName);
    }
}
A: 

Are you using spring's ApplicationContext, or BeanFactory? If you are using ApplicationContext, you can implement ApplicationContextAware instead and spring will supply you with the application context. I've never used spring's BeanFactory before but I have used ApplicationContext and it works.

Miguel Ping
It doesn't matter whether I use ApplicationContextAware or BeanFactoryAware. Neither works for me. It appears the issue is that the preConstruction=true option does not work in conjunction with objects created via readResolve().
bmatthews68
A: 

Are you sure that your Configurable class has been properly weaved wither by compiling it with the ApsectJ compiler or with runtime weaving.

You also need to specify the attributes in you configuration file marking the bean as prototype. Something along the lines of:

<aop:spring-configured />
<bean class="package.name.SerializedCGLibEntity" scope="prototype">
<property name="beanFactory" value="whateverValue"/>
</bean>

krumpi
Yes. It has definitely been weaved. I've put trace statements in the default constructor and they never get called.
bmatthews68