Bug 36113 - Session persistence for objects with primitive types
Session persistence for objects with primitive types
Status: RESOLVED FIXED
Product: Tomcat 5
Classification: Unclassified
Component: Catalina
Unknown
All other
: P2 normal (vote)
: ---
Assigned To: Tomcat Developers Mailing List
:
Depends on:
Blocks:
  Show dependency tree
 
Reported: 2005-08-10 10:51 UTC by Marcus Haarmann
Modified: 2005-09-08 09:41 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Marcus Haarmann 2005-08-10 10:51:52 UTC
When there is a primitive data type (like int or boolean) contained in an 
object which is stored in a session, a persistence will fail when reading back 
the session data.
This is caused by a bug in org.apache.catalina.util.CustomObjectInputStream, 
which does not care for primitive types, as ObjectInputstream is doing since 
JDK1.4
The following code is copied from the SUN Java sources and works correctly:
    /** table mapping primitive type names to corresponding class objects */
    private static final HashMap primClasses = new HashMap(8, 1.0F);
    static {
            primClasses.put("boolean", boolean.class);
            primClasses.put("byte", byte.class);
            primClasses.put("char", char.class);
            primClasses.put("short", short.class);
            primClasses.put("int", int.class);
            primClasses.put("long", long.class);
            primClasses.put("float", float.class);
            primClasses.put("double", double.class);
            primClasses.put("void", void.class);
    } 

    public Class resolveClass(ObjectStreamClass classDesc)
        throws ClassNotFoundException, IOException {
        try {
          return Class.forName(classDesc.getName(), false, classLoader);
        }
        catch (ClassNotFoundException ex) {
          Class cl = (Class) primClasses.get(classDesc.getName ());
          if (cl != null) {
             return cl;
          } else {
             throw ex;
          }
        }
    } 

This leads to the ability to store any primitive type (like a castor object) 
inside a persisted session. Maybe somebody can commit this to the tomcat trunk.

Thanks, Marcus
Comment 1 Remy Maucherat 2005-08-18 15:11:02 UTC
This works very well for me. Please provide a ready to test WAR file.
Comment 2 Marcus Haarmann 2005-08-18 15:21:00 UTC
I already tried that code, patching a copy of tomcat myself and this works.
So this can be treated as solved.
Just one remark:
The same bug is in the cluster package, class ReplicationStream. Whenever a 
primitive type is in the session (such as a castor object), an exception is 
generated. Maybe you should fix this inside that class also.
Comment 3 Remy Maucherat 2005-08-18 15:27:53 UTC
I asked for a test case, not further comments, so please don't reopen the report
without a test WAR. This works for me without any changes.
Comment 4 Marcus Haarmann 2005-09-08 10:32:27 UTC
Hi, I tried to reproduce the problem using a simple program but I did fail 
(boolean seems to get converted to java.lang.Boolean in any case I tried).
The problem is occurring using a third party library and I am not sure what 
these guys are doing to contain primitive types in the session. This 
application does not work without a very big environment, so it is not possible 
for me to provide a war to test the behaviour.

The proble also occurs in Replication, where the same bug exists.

See the stack trace (from 5.5.9, builtin in JBoss):
08.09.2005 09:46:59.724 titan ERROR - ClassNotFoundException while loading 
persisted sessions: java.lang.ClassNotFoundException: boolean
java.lang.ClassNotFoundException: boolean
	at org.apache.catalina.loader.WebappClassLoader.loadClass
(WebappClassLoader.java:1332)
	at org.apache.catalina.loader.WebappClassLoader.loadClass
(WebappClassLoader.java:1181)
	at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:242)
	at org.apache.catalina.util.CustomObjectInputStream.resolveClass
(CustomObjectInputStream.java:73)
	at java.io.ObjectInputStream.readNonProxyDesc
(ObjectInputStream.java:1538)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1460)
	at java.io.ObjectInputStream.readClass(ObjectInputStream.java:1427)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1282)
	at java.io.ObjectInputStream.defaultReadFields
(ObjectInputStream.java:1912)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1836)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:339)
	at java.util.LinkedList.readObject(LinkedList.java:776)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:585)
	at java.io.ObjectStreamClass.invokeReadObject
(ObjectStreamClass.java:919)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1813)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.defaultReadFields
(ObjectInputStream.java:1912)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1836)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:339)
	at java.util.ArrayList.readObject(ArrayList.java:587)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:585)
	at java.io.ObjectStreamClass.invokeReadObject
(ObjectStreamClass.java:919)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1813)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.defaultReadFields
(ObjectInputStream.java:1912)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1836)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:339)
	at java.util.ArrayList.readObject(ArrayList.java:587)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:585)
	at java.io.ObjectStreamClass.invokeReadObject
(ObjectStreamClass.java:919)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1813)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.defaultReadFields
(ObjectInputStream.java:1912)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1836)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.defaultReadFields
(ObjectInputStream.java:1912)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1836)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1299)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:339)
	at java.util.ArrayList.readObject(ArrayList.java:587)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25) 
Comment 5 Remy Maucherat 2005-09-08 13:27:09 UTC
(In reply to comment #4)
> Hi, I tried to reproduce the problem using a simple program but I did fail 
> (boolean seems to get converted to java.lang.Boolean in any case I tried).
> The problem is occurring using a third party library and I am not sure what 
> these guys are doing to contain primitive types in the session. This 
> application does not work without a very big environment, so it is not possible 
> for me to provide a war to test the behaviour.
> 
> The proble also occurs in Replication, where the same bug exists.

Ok, I understand (to some extent: it's weird). One of the issues is that I don't
feel confident cut & pasting Sun's code.

Can you test to see if this change works fine with your webapp:

    public Class resolveClass(ObjectStreamClass classDesc)
        throws ClassNotFoundException, IOException {
        try {
         return Class.forName(classDesc.getName(), false, classLoader);
        } catch (ClassNotFoundException e) {
         return super.resolveClass(classDesc);
        }
    }

?
Comment 6 Marcus Haarmann 2005-09-08 15:30:27 UTC
Hi, I tried your code and it works correctly for session persistence in my case.
Although my approch would be faster (because only one exception is caught until 
the result is returned in case of a primitive type), I agree to better not cut 
and paste code (this is object orientated programming, isn't it ?).
Keep in mind to also change the ReplicationStream class in clustering.
Something like:
public Class resolveClass(ObjectStreamClass classDesc)
        throws ClassNotFoundException, IOException {
        String name = classDesc.getName();
        boolean tryRepFirst = name.startsWith("org.apache.catalina.cluster");
        try {
          try
          {
            if ( tryRepFirst ) return findReplicationClass(name);
            else return findWebappClass(name);
          }
          catch ( Exception x )
          {
            if ( tryRepFirst ) return findWebappClass(name);
            else return findReplicationClass(name);
          }
        }
        catch (ClassNotFoundException e)
        {
          return super.resolveClass (classDesc);
        }
    }
     
Thanks, Marcus
Comment 7 Remy Maucherat 2005-09-08 17:41:53 UTC
I have applied the proposed patch.