3

I'm trying to read a serialized Java file containing instances of classes I don't have in my classpath while reading.

Is there a way (perhaps by writing my own ObjectInputStream?) to ignore those ClassNotFoundException and replace the corresponding object of the stream by null ?

The object I want to read is similar to this one :

public class Log {
    private String someField;
    private Throwable throwable;
}

Actually, that Log object is read, but I don't have in my classpath the concrete class of some Log.throwable values. I would want that in that case, the throwable field value would be null but I want my Log object with the other fields read.

If I catch the exception, I couldn't even have my Log object.

2
  • 1
    Why don't you simply catch the exception and return null? Commented Dec 12, 2013 at 12:35
  • 1
    Because it happens on a field of the object I want to read, so I want my parent object to be read, and the problematic field to be set to null. Commented Dec 12, 2013 at 14:51

2 Answers 2

2

Actually, I have tried multiple way to do this (extend ObjectInputStream and implement ObjectInputStream.readClassDescriptor() in order to return a Proxy of an ObjectStreamClass which would return null for default method ObjectStreamClass.getResolveException(), using Javassist because JDK cannot proxify classes, but the problem is : ObjectStreamClass cannot be instantiated outside of java.io package).

But I finally found a (rather ugly) way to do this :

public class DecompressibleObjectInputStream extends ObjectInputStream {
    private static Logger logger = LoggerFactory.getLogger(DecompressibleObjectInputStream.class);

    public DecompressibleObjectInputStream(InputStream in) throws IOException {
        super(in);

        try {
            // activating override on readObject thanks to https://stackoverflow.com/a/3301720/535203
            Field enableOverrideField = ObjectInputStream.class.getDeclaredField("enableOverride");

            enableOverrideField.setAccessible(true);

            Field fieldModifiersField = Field.class.getDeclaredField("modifiers");
            fieldModifiersField.setAccessible(true);
            fieldModifiersField.setInt(enableOverrideField, enableOverrideField.getModifiers() & ~Modifier.FINAL);

            enableOverrideField.set(this, true);
        } catch (NoSuchFieldException e) {
            warnCantOverride(e);
        } catch (SecurityException e) {
            warnCantOverride(e);
        } catch (IllegalArgumentException e) {
            warnCantOverride(e);
        } catch (IllegalAccessException e) {
            warnCantOverride(e);
        }
    }

    private void warnCantOverride(Exception e) {
        logger.warn("Couldn't enable readObject override, won't be able to avoid ClassNotFoundException while reading InputStream", e);
    }
    @Override
    public void defaultReadObject() throws IOException, ClassNotFoundException {
        try {
            super.defaultReadObject();
        } catch (ClassNotFoundException e) {
            logger.warn("Potentially Fatal Deserialization Operation.", e);
        }
    }

    @Override
    protected Object readObjectOverride() throws IOException, ClassNotFoundException {
    // copy of JDK 7 code avoiding the ClassNotFoundException to be thrown :
        /*
            // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
         */
        try {
        int outerHandle = getObjectInputStreamFieldValue("passHandle");
        int depth = getObjectInputStreamFieldValue("depth");
        try {
            Object obj = callObjectInputStreamMethod("readObject0", new Class<?>[] {boolean.class}, false);
            Object handles = getObjectInputStreamFieldValue("handles");
            Object passHandle = getObjectInputStreamFieldValue("passHandle");
            callMethod(handles, "markDependency", new Class<?>[] {int.class, int.class}, outerHandle, passHandle);

            ClassNotFoundException ex = callMethod(handles, "lookupException", new Class<?>[] {int.class},  passHandle);

            if (ex != null) {
                logger.warn("Avoiding exception", ex);
            }
            if (depth == 0) {
                callMethod(getObjectInputStreamFieldValue("vlist"), "doCallbacks", new Class<?>[] {});
            }
            return obj;
        } finally {
            getObjectInputStreamField("passHandle").setInt(this, outerHandle);
            boolean closed = getObjectInputStreamFieldValue("closed");
            if (closed && depth == 0) {
                callObjectInputStreamMethod("clear", new Class<?>[] {});
            }
        }
        } catch (NoSuchFieldException e) {
            throw createCantMimicReadObject(e);
        } catch (SecurityException e) {
            throw createCantMimicReadObject(e);
        } catch (IllegalArgumentException e) {
            throw createCantMimicReadObject(e);
        } catch (IllegalAccessException e) {
            throw createCantMimicReadObject(e);
        } catch (InvocationTargetException e) {
            throw createCantMimicReadObject(e);
        } catch (NoSuchMethodException e) {
            throw createCantMimicReadObject(e);
        } catch (Throwable t) {
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            }
            if (t instanceof IOException) {
                throw (IOException)t;
            }
            throw createCantMimicReadObject(t);
        }
    }

    private IllegalStateException createCantMimicReadObject(Throwable t) {
        return new IllegalStateException("Can't mimic JDK readObject method", t);
    }

    @SuppressWarnings("unchecked")
    private <T> T getObjectInputStreamFieldValue(String fieldName) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        Field declaredField = getObjectInputStreamField(fieldName);
        return (T) declaredField.get(this);
    }

    private Field getObjectInputStreamField(String fieldName) throws NoSuchFieldException {
        Field declaredField = ObjectInputStream.class.getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        return declaredField;
    }

    @SuppressWarnings("unchecked")
    private <T> T callObjectInputStreamMethod(String methodName, Class<?>[] parameterTypes, Object... args) throws Throwable {
        Method declaredMethod = ObjectInputStream.class.getDeclaredMethod(methodName, parameterTypes);
        declaredMethod.setAccessible(true);
        try {
            return (T) declaredMethod.invoke(this, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T callMethod(Object object, String methodName, Class<?>[] parameterTypes, Object... args) throws Throwable {
        Method declaredMethod = object.getClass().getDeclaredMethod(methodName, parameterTypes);
        declaredMethod.setAccessible(true);
        try {
            return (T) declaredMethod.invoke(object, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }
}

Then I overrode the ObjectInputStream.readClassDescriptor() in order to ignore differences between serialVersionUID also (as described in that answer) and I've got an ObjectInputStream which can read nearly everything !

Sign up to request clarification or add additional context in comments.

Comments

0

I don't think there is a way to do this ... apart from cloning and modifying the Java serialization implementation.

Certainly, readObject and readResolve hooks won't help, because they rely on methods of the class that you cannot load.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.