Search code examples
javahibernategwttransientgilead

TransientObjectException error occurs after first save


I am getting a TransientObjectException whenever I make a consecutive save; I do not get this error on the first save or if I refresh the screen/page. This is pretty confusing to me since once the object has been saved sucessfully it should not be transient, since it has a representation in my DB.

My files below (User and UserPasswordInfo) have a one-to-many relationship, where a User can have more than one UserPasswordInfo (or UPI) and they are linked by my User's primary key: user_id.

I am pretty sure the problem exists from the following files, and not elsewhere, since the TOE error only showed up after adding this and, from my knowledge, the software has never had this problem in its lifetime (a few years).

Here are my hibernate files:

User.hbm.xml

<hibernate-mapping>
    <class name="com.app.common.domain.User" table="users"  >
        <id name="id" type="java.lang.Integer">
            <column name="user_id" />
            <generator class="identity" />
        </id>

       <set name="passwordInfos" table="user_password_info" lazy="false" cascade="all" fetch="select">
            <key>
                <column name="user_id"></column>
            </key>
            <one-to-many class="com.app.common.domain.UserPasswordInfo" />
        </set>        

    </class>
</hibernate-mapping>

UserPasswordInfo.hbm.xml

<hibernate-mapping>
   <class name="com.app.common.domain.UserPasswordInfo" table="user_password_info">
      <id name="passwordInfoId" type="java.lang.Integer">
            <column name="password_info_id" />
            <generator class="identity" />
       </id>

      <property name="password" column="password" type="java.lang.String"/>
      <property name="passwordTS" column="password_ts" type="java.sql.Timestamp"/>
      <property name="pwdChange" column="initial_password_change" type="java.lang.Boolean"/>
      <property name="userId" column="user_id" type="java.lang.Integer"/>

   </class>
</hibernate-mapping>

Here are their Java files:

User.java

import java.sql.Timestamp;
import java.util.Collections;
import java.util.Comparator;

import java.util.HashSet;
import java.util.Set;

public class User extends DBObject {//DBObject has a getter and setter for the id

    private Set<UserPasswordInfo> passwordInfos = new HashSet<UserPasswordInfo>(0);

    public User() {
        super();
    }

    .
    .
    .

    public Set<UserPasswordInfo> getPasswordInfos() {
        return passwordInfos;
    }

    public void setPasswordInfos(Set<UserPasswordInfo> passwordInfos) {
        this.passwordInfos = passwordInfos;
    }

    public void addUPI(String pwd, Timestamp pwdTS, Boolean pwdChng){
        UserPasswordInfo upi = new UserPasswordInfo();
        upi.setPassword(pwd);
        upi.setPasswordTS(pwdTS);
        upi.setPwdChange(pwdChng);
        if(getId() != null)
            upi.setUserId(getId().intValue());
        passwordInfos.add(upi);
    }

    public String getLatestPassword(){
        return getSortedList().get(0).getPassword();
    }

    public UserPasswordInfo getLatestUPI(){
        ArrayList<UserPasswordInfo> list = getSortedList();
        return (list == null || list.size() == 0) ? null : list.get(0);
    }

    public ArrayList<String> getLastThreePasswords(){

        final ArrayList<UserPasswordInfo> upiList = getSortedList();

        if(upiList == null)
            return null;

        ArrayList<String> list = new ArrayList<String>();
        int i = 0; 
        while(i < 3 && i < list.size()){
            list.add(upiList.get(i++).getPassword());
        }
        return list;
    }

    public ArrayList<UserPasswordInfo> getSortedList(){

        ArrayList<UserPasswordInfo> upi = new ArrayList<UserPasswordInfo>(getPasswordInfos());
        if(upi == null || upi.size() == 0){
            return null;
        }

        Collections.sort(upi, new Comparator<UserPasswordInfo>() {
            public int compare(UserPasswordInfo o1, UserPasswordInfo o2) {
                if (o1.getPasswordTS() == null || o2.getPasswordTS() == null)
                    return 0;
                return o2.getPasswordTS().compareTo(o1.getPasswordTS());
            }
        });
        return upi;
    }
}

UserPasswordInfo.java

import java.sql.Timestamp;

public class UserPasswordInfo extends DBObject{

    private int passwordInfoId;
    private String password;
    private Timestamp passwordTS;
    private Boolean pwdChange;
    private int userId;

    public UserPasswordInfo(){}

    public UserPasswordInfo(String pwd, Timestamp pwdTS, Boolean pwdChng){
        password = pwd;
        passwordTS = pwdTS;
        pwdChange = pwdChng;
    }

    public UserPasswordInfo(int userId, String pwd, Timestamp pwdTS, Boolean pwdChng){
        this.userId = userId;
        password = pwd;
        passwordTS = pwdTS;
        pwdChange = pwdChng;
    }

    public int getPasswordInfoId(){
        return passwordInfoId;
    }
    public void setPasswordInfoId(int pid){
        passwordInfoId = pid;
    }
    public String getPassword(){
        return password;
    }
    public void setPassword(String pwd){
        password = pwd;
    }
    public Timestamp getPasswordTS(){
        return passwordTS;
    }
    public void setPasswordTS(Timestamp ts){
        passwordTS = ts;
    }
    public void setPwdChange(Boolean pwdChange) {
        this.pwdChange = pwdChange;
    }
    public Boolean getPwdChange() {
        return pwdChange;
    }
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
}

Here is error message:

ERROR BeanPopulator - 
propertyName=passwordInfos
readerMethod=public java.util.Set com.app.domain.User.getPasswordInfos()
setterMethod=public void com.app.common.domain.User.setPasswordInfos(java.util.Set)
fromBean=[User someUser, id: 268, role: front desk]
toBean=[User , id: null]

net.sf.gilead.exception.TransientObjectException
    at net.sf.gilead.core.hibernate.HibernateUtil.getId(HibernateUtil.java:316)
    at net.sf.gilead.core.hibernate.HibernateUtil.getId(HibernateUtil.java:224)
    at net.sf.gilead.core.hibernate.HibernateUtil.loadPersistentCollection(HibernateUtil.java:854)
    at net.sf.gilead.core.hibernate.HibernateUtil.createPersistentCollection(HibernateUtil.java:843)
    at net.sf.gilead.core.beanlib.merge.MergeCollectionReplicator.replicateCollection(MergeCollectionReplicator.java:119)
    at net.sf.beanlib.provider.replicator.ReplicatorTemplate.replicate(ReplicatorTemplate.java:112)
    at net.sf.beanlib.provider.BeanTransformer.transform(BeanTransformer.java:231)
    at net.sf.beanlib.provider.BeanPopulator.doit(BeanPopulator.java:201)
    at net.sf.beanlib.provider.BeanPopulator.processSetterMethod(BeanPopulator.java:172)
    at net.sf.beanlib.provider.BeanPopulator.populate(BeanPopulator.java:269)
    at net.sf.gilead.core.LazyKiller.populate(LazyKiller.java:288)
    at net.sf.gilead.core.LazyKiller.attach(LazyKiller.java:237)
    at net.sf.gilead.core.PersistentBeanManager.mergePojo(PersistentBeanManager.java:554)
    at net.sf.gilead.core.PersistentBeanManager.merge(PersistentBeanManager.java:318)
    at net.sf.gilead.core.PersistentBeanManager.mergeCollection(PersistentBeanManager.java:581)
    at net.sf.gilead.core.PersistentBeanManager.merge(PersistentBeanManager.java:290)
    at net.sf.gilead.gwt.GileadRPCHelper.parseInputParameters(GileadRPCHelper.java:94)
    at net.sf.gilead.gwt.GileadRPCHelper.parseInputParameters(GileadRPCHelper.java:137)
    at net.sf.gilead.gwt.PersistentRemoteService.processCall(PersistentRemoteService.java:172)

EDIT Here are some links and sections of code that may be related to this error:

This is the method call that's made when I save the objects (the above error is thrown before this method is called)

@SuppressWarnings({ "rawtypes" })
    private List saveObjectList(List<? extends DBObject> values) {
        Session session = sessionFactory.getCurrentSession();
        Transaction tx = null;

        try {
            tx = session.beginTransaction();
            for (DBObject value : values) {
                if (value.getIsDirty()) {
                    Integer clientId = value.getClientId();
                    if (value.getId() != null) {//enters here
                        value = (DBObject) session.merge(value);
                        session.persist(value);
                    }
                    else {
                        session.save(value);
                    }
                    value.setClientId(clientId);
                    value.setIsDirty(false);
                }
            }
            tx.commit();//fails here

            return values;
        }
        catch (Exception ex) {
            String msg = "Failed to save values. ";
            if (values == null) {
                msg += " Values to save were null.";
            }
            else {
                msg += " Values to save had length: " + values.size();
            }
            rollbackOrCloseSession(tx, session, "saveObjectList() - " + msg, ex);
            return null;
        }
    }

GileadRPCHelper

/**
     * Parse RPC input parameters.
     * Must be called before GWT service invocation.
     * @param rpcRequest the input GWT RPC request
     * @param beanManager the Hibernate bean manager
     * @param session the HTTP session (for HTTP Pojo store)
     */
    public static void parseInputParameters(Object[] parameters, 
                                            PersistentBeanManager beanManager,
                                            HttpSession session)
    {
    //  Init classloader for proxy mode
    //
        if (beanManager.getClassMapper() instanceof ProxyClassMapper)
        {
            initClassLoader();
        }

    //  Set HTTP session of Pojo store in thread local
    //
        HttpSessionProxyStore.setHttpSession(session);

    //  Merge parameters if needed
    //
        if (parameters != null)
        {
            long start = System.currentTimeMillis();
            for (int index = 0 ; index < parameters.length; index ++)
            {
                if (parameters[index] != null)
                {
                    try
                    {
                        //***ERROR occurs when this is called
                        parameters[index] = beanManager.merge(parameters[index], true);
                    }
                    catch (NotAssignableException ex)
                    {
                        log.debug(parameters[index] + " not assignable");
                    }
                    catch (TransientObjectException ex)
                    {
                        log.info(parameters[index] + " is transient : cannot merge...");
                    }
                }
            }

            if (log.isDebugEnabled())
            {
                log.debug("Merge took " + (System.currentTimeMillis() - start) + " ms.");
            }
        }
    }

PersistentBeanManager

/**
     * Merge the clone POJO to its Hibernate counterpart
     */
    public Object merge(Object object) {
        // Explicit merge
        return merge(object, false);
    }

    /**
     * Merge the clone POJO to its Hibernate counterpart
     */
    @SuppressWarnings("unchecked")
    public Object merge(Object object, boolean assignable) {
        // Precondition checking
        //
        if (object == null) {
            return null;
        }

        if (_persistenceUtil == null) {
            throw new RuntimeException("No Persistence Util set !");
        }

        // Collection handling
        //
        if (object instanceof Collection) {
            return mergeCollection((Collection) object, assignable);
        } else if (object instanceof Map) {
            return mergeMap((Map) object, assignable);
        } else if (object.getClass().isArray()) {
            // Check primitive type
            //
            if (object.getClass().getComponentType().isPrimitive()) {
                return object;
            }

            // Merge as a collection
            //
            Object[] array = (Object[]) object;
            Collection result = mergeCollection(Arrays.asList(array), assignable);

            // Get the result as an array (much more tricky !!!)
            //
            Class<?> componentType = object.getClass().getComponentType();
            Object[] copy = (Object[]) java.lang.reflect.Array.newInstance(componentType, array.length);
            return result.toArray(copy);
        } else {
            return mergePojo(object, assignable);
        }
    }

/**
     * Retrieve the Hibernate Pojo and merge the modification from GWT
     * 
     * @param clonePojo the clone pojo
     * @param assignable does the source and target class must be assignable
     * @return the merged Hibernate POJO
     * @exception UnsupportedOperationException if the clone POJO does not implements ILightEntity and the POJO store is
     *                stateless
     * @exception NotAssignableException if source and target class are not assignable
     */
    protected Object mergePojo(Object clonePojo, boolean assignable) {

        // Get Hibernate associated class
        Class<?> cloneClass = clonePojo.getClass();
        Class<?> hibernateClass = null;
        if (_classMapper != null) {
            hibernateClass = _classMapper.getSourceClass(cloneClass);
        }
        if (hibernateClass == null) {
            // Not a clone : take the inner class
            hibernateClass = clonePojo.getClass();
        }

        // Precondition checking : is the pojo managed by Hibernate
        if (_persistenceUtil.isPersistentClass(hibernateClass) == true) {
            // Assignation checking
            if ((assignable == true) && (hibernateClass.isAssignableFrom(cloneClass) == false)) {
                throw new NotAssignableException(hibernateClass, cloneClass);
            }
        }

        // Retrieve the pojo
        try {
            Serializable id = null;
            try {
                id = _persistenceUtil.getId(clonePojo, hibernateClass);
                if (id == null) {
                    _log.info("HibernatePOJO not found : can be transient or deleted data : " + clonePojo);
                }
            } catch (TransientObjectException ex) {
                _log.info("Transient object : " + clonePojo);
            } catch (NotPersistentObjectException ex) {
                if (holdPersistentObject(clonePojo) == false) {
                    // Do not merge not persistent instance, since they do not
                    // necessary
                    // implement the Java bean specification
                    //
                    if (_log.isDebugEnabled()) {
                        _log.debug("Not persistent object, merge is not needed : " + clonePojo);
                    }
                    return clonePojo;
                } else {
                    if (_log.isDebugEnabled()) {
                        _log.debug("Merging wrapper object : " + clonePojo);
                    }
                }
            }

            if (ClassUtils.immutable(hibernateClass)) {
                // Do not clone immutable types
                //
                return clonePojo;
            }

            // Create a new POJO instance
            //
            Object hibernatePojo = null;
            try {
                if (AnnotationsManager.hasGileadAnnotations(hibernateClass)) {
                    if (id != null) {
                        // ServerOnly or ReadOnly annotation : load from DB
                        // needed
                        //
                        hibernatePojo = _persistenceUtil.load(id, hibernateClass);
                    } else {
                        // Transient instance
                        //
                        hibernatePojo = clonePojo;
                    }
                } else {
                    Constructor<?> constructor = hibernateClass.getDeclaredConstructor(new Class<?>[] {});
                    constructor.setAccessible(true);
                    hibernatePojo = constructor.newInstance();
                }
            } catch (Exception e) {
                throw new RuntimeException("Cannot create a fresh new instance of the class " + hibernateClass, e);
            }

            // Merge the modification in the Hibernate Pojo
            //
            _lazyKiller.attach(hibernatePojo, clonePojo);
            return hibernatePojo;
        } finally {
            _persistenceUtil.closeCurrentSession();
            _proxyStore.cleanUp();
        }
    }

BeanPopulator

@Override
    public <T> T populate() {
        if (getBeanTransformerSpi() != null) {
            getBeanTransformerSpi().getClonedMap().put(fromBean, toBean);
        }
        // invoking all declaring setter methods of toBean from all matching getter methods of fromBean
        for (Method m : baseConfig.getSetterMethodCollector().collect(toBean)) {
            processSetterMethod(m);
        }
        @SuppressWarnings("unchecked")
        T ret = (T) toBean;
        return ret;
    }

 private void processSetterMethod(Method setterMethod) {
        String methodName = setterMethod.getName();
        final String propertyString = methodName.substring(baseConfig.getSetterMethodCollector().getMethodPrefix().length());

        if (baseConfig.isDebug()) {
            if (log.isInfoEnabled()) {
                log.info(new StringBuilder("processSetterMethod: processing propertyString=").append(propertyString).append("")
                        .append(", fromClass=").append(fromBean.getClass()).append(", toClass=").append(toBean.getClass()).toString());
            }
        }
        Method readerMethod = baseConfig.getReaderMethodFinder().find(propertyString, fromBean);

        if (readerMethod == null) {
            return;
        }
        // Reader method of fromBean found
        Class<?> paramType = setterMethod.getParameterTypes()[0];
        String propertyName = Introspector.decapitalize(propertyString);
        try {
            doit(setterMethod, readerMethod, paramType, propertyName);
        } catch (Exception ex) {
            baseConfig.getBeanPopulationExceptionHandler().initFromBean(fromBean).initToBean(toBean).initPropertyName(propertyName)
                    .initReaderMethod(readerMethod).initSetterMethod(setterMethod).handleException(ex, log);
        }
    }

    private <T> void doit(Method setterMethod, Method readerMethod, Class<T> paramType, final String propertyName) {
        if (baseConfig.getDetailedPropertyFilter() != null) {
            if (!baseConfig.getDetailedPropertyFilter().propagate(propertyName, fromBean, readerMethod, toBean, setterMethod)) {
                return;
            }
        }
        if (baseConfig.getPropertyFilter() != null) {
            if (!baseConfig.getPropertyFilter().propagate(propertyName, readerMethod)) {
                return;
            }
        }
        Object propertyValue = this.invokeMethodAsPrivileged(fromBean, readerMethod, null);

        if (baseConfig.getBeanSourceHandler() != null) {
            baseConfig.getBeanSourceHandler().handleBeanSource(fromBean, readerMethod, propertyValue);
        }

        if (transformer != null) {
            PropertyInfo propertyInfo = new PropertyInfo(propertyName, fromBean, toBean);
            propertyValue = transformer.transform(propertyValue, paramType, propertyInfo);
        }

        if (baseConfig.isDebug()) {
            if (log.isInfoEnabled()) {
                log.info("processSetterMethod: setting propertyName=" + propertyName);
            }
        }
        // Invoke setter method
        Object[] args = { propertyValue };
        this.invokeMethodAsPrivileged(toBean, setterMethod, args);
        return;
    }

Gilead HibernateUtil class


Solution

  • Potentially this might be caused by the following code:

    public void addUPI(String pwd, Timestamp pwdTS, Boolean pwdChng){
        UserPasswordInfo upi = new UserPasswordInfo();
        upi.setPassword(pwd);
        upi.setPasswordTS(pwdTS);
        upi.setPwdChange(pwdChng);
        if(getId() != null)
            upi.setUserId(getId().intValue());
        passwordInfos.add(upi);
    }
    

    If you invoke this method and then store your User object, I would assume the exception to be thrown. The thing is that you need to attach UserPasswordInfo to the context first, and only then you can add it to the User object.

    So the correct sequence is to store UserPasswordInfo first:

    entityManager.persist(upi);
    

    And then add it to passwords and store the user object:

    user.getPasswordInfos().add(upi);
    entityManager.save(user);