org.apache.wicket.serialize.java.JavaSerializer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.wicket.serialize.java.JavaSerializer.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.wicket.serialize.java;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;

import org.apache.wicket.Application;
import org.apache.wicket.ThreadContext;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.application.IClassResolver;
import org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream;
import org.apache.wicket.core.util.objects.checker.ObjectSerializationChecker;
import org.apache.wicket.serialize.ISerializer;
import org.apache.wicket.settings.ApplicationSettings;
import org.apache.wicket.util.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An implementation of {@link ISerializer} based on Java Serialization (ObjectOutputStream,
 * ObjectInputStream)
 * 
 * Requires the application key to enable serialization and deserialisation outside thread in which
 * application thread local is set
 */
public class JavaSerializer implements ISerializer {
    private static final Logger log = LoggerFactory.getLogger(JavaSerializer.class);
    /**
     * The key of the application which can be used later to find the proper {@link IClassResolver}
     */
    private final String applicationKey;

    /**
     * Construct.
     * 
     * @param applicationKey
     *      the name of the application
     */
    public JavaSerializer(final String applicationKey) {
        this.applicationKey = applicationKey;
    }

    @Override
    public byte[] serialize(final Object object) {
        try {
            final ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream oos = null;
            try {
                oos = newObjectOutputStream(out);
                oos.writeObject(applicationKey);
                oos.writeObject(object);
            } finally {
                try {
                    IOUtils.close(oos);
                } finally {
                    out.close();
                }
            }
            return out.toByteArray();
        } catch (Exception e) {
            log.error("Error serializing object " + object.getClass() + " [object=" + object + "]", e);
        }
        return null;
    }

    @Override
    public Object deserialize(final byte[] data) {
        ThreadContext old = ThreadContext.get(false);
        final ByteArrayInputStream in = new ByteArrayInputStream(data);
        ObjectInputStream ois = null;
        try {
            Application oldApplication = ThreadContext.getApplication();
            try {
                ois = newObjectInputStream(in);
                String applicationName = (String) ois.readObject();
                if (applicationName != null) {
                    Application app = Application.get(applicationName);
                    if (app != null) {
                        ThreadContext.setApplication(app);
                    }
                }
                return ois.readObject();
            } finally {
                try {
                    ThreadContext.setApplication(oldApplication);
                    IOUtils.close(ois);
                } finally {
                    in.close();
                }
            }
        } catch (ClassNotFoundException | IOException cnfx) {
            throw new RuntimeException("Could not deserialize object from byte[]", cnfx);
        } finally {
            ThreadContext.restore(old);
        }
    }

    /**
     * Gets a new instance of an {@link ObjectInputStream} with the provided {@link InputStream}.
     * 
     * @param in
     *            The input stream that should be used for the reading
     * @return a new object input stream instance
     * @throws IOException
     *             if an I/O error occurs while reading stream header
     */
    protected ObjectInputStream newObjectInputStream(InputStream in) throws IOException {
        return new ClassResolverObjectInputStream(in);
    }

    /**
     * Gets a new instance of an {@link ObjectOutputStream} with the provided {@link OutputStream}.
     * 
     * @param out
     *            The output stream that should be used for the writing
     * @return a new object output stream instance
     * @throws IOException
     *             if an I/O error occurs while writing stream header
     */
    protected ObjectOutputStream newObjectOutputStream(OutputStream out) throws IOException {
        return new SerializationCheckerObjectOutputStream(out);
    }

    /**
     * Extend {@link ObjectInputStream} to add framework class resolution logic.
     */
    private static class ClassResolverObjectInputStream extends ObjectInputStream {
        public ClassResolverObjectInputStream(InputStream in) throws IOException {
            super(in);
        }

        // This override is required to resolve classes inside in different bundle, i.e.
        // The classes can be resolved by OSGI classresolver implementation
        @Override
        protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
            try {
                return super.resolveClass(desc);
            } catch (ClassNotFoundException cnfEx) {
                // ignore this exception.
                log.debug("Class not found by the object outputstream itself, trying the IClassResolver");

                Class<?> candidate = resolveClassInWicket(desc.getName());
                if (candidate == null) {
                    throw cnfEx;
                }
                return candidate;
            }
        }

        /*
         * resolves a class by name, first using the default Class.forName, but looking in the
         * Wicket ClassResolvers as well.
         */
        private Class<?> resolveClassByName(String className, ClassLoader latestUserDefined)
                throws ClassNotFoundException {
            try {
                return Class.forName(className, false, latestUserDefined);
            } catch (ClassNotFoundException cnfEx) {
                Class<?> ret = resolveClassInWicket(className);
                if (ret == null)
                    throw cnfEx;
                return ret;
            }
        }

        /*
         * Resolves a class from Wicket's ClassResolver
         */
        private Class<?> resolveClassInWicket(String className) throws ClassNotFoundException {
            Class<?> candidate;
            try {
                Application application = Application.get();
                ApplicationSettings applicationSettings = application.getApplicationSettings();
                IClassResolver classResolver = applicationSettings.getClassResolver();

                candidate = classResolver.resolveClass(className);
            } catch (WicketRuntimeException ex) {
                if (ex.getCause() instanceof ClassNotFoundException) {
                    throw (ClassNotFoundException) ex.getCause();
                } else {
                    ClassNotFoundException wrapperCnf = new ClassNotFoundException();
                    wrapperCnf.initCause(ex);
                    throw wrapperCnf;
                }
            }
            return candidate;
        }

        /*
         * This method is an a copy of the super-method, with Class.forName replaced with a call to
         * resolveClassByName.
         */
        @Override
        protected Class<?> resolveProxyClass(String[] interfaces) throws ClassNotFoundException, IOException {
            try {
                return super.resolveProxyClass(interfaces);
            } catch (ClassNotFoundException cnfEx) {
                // ignore this exception.
                log.debug("Proxy Class not found by the object outputstream itself, trying the IClassResolver");

                ClassLoader latestLoader = latestUserDefinedLoader();
                ClassLoader nonPublicLoader = null;
                boolean hasNonPublicInterface = false;

                // define proxy in class loader of non-public interface(s), if any
                Class<?>[] classObjs = new Class<?>[interfaces.length];
                for (int i = 0; i < interfaces.length; i++) {
                    Class<?> cl = resolveClassByName(interfaces[i], latestLoader);
                    if ((cl.getModifiers() & Modifier.PUBLIC) == 0) {
                        if (hasNonPublicInterface) {
                            if (nonPublicLoader != cl.getClassLoader()) {
                                throw new IllegalAccessError("conflicting non-public interface class loaders");
                            }
                        } else {
                            nonPublicLoader = cl.getClassLoader();
                            hasNonPublicInterface = true;
                        }
                    }
                    classObjs[i] = cl;
                }
                try {
                    return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : latestLoader, classObjs);
                } catch (IllegalArgumentException e) {
                    throw new ClassNotFoundException(null, e);
                }
            }
        }

        /*
         * Method in the superclass is private, call it via reflection.
         */
        private static ClassLoader latestUserDefinedLoader() {
            try {
                Method originalMethod = ObjectInputStream.class.getDeclaredMethod("latestUserDefinedLoader");
                originalMethod.setAccessible(true);
                return (ClassLoader) originalMethod.invoke(null);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                    | NoSuchMethodException | SecurityException e) {
                // should not happen
                throw new WicketRuntimeException(e);
            }
        }
    }

    /**
     * Write objects to the wrapped output stream and log a meaningful message for serialization
     * problems.
     *
     *  <p>
     *     Note: the checking functionality is used only if the serialization fails with NotSerializableException.
     *     This is done so to save some CPU time to make the checks for no reason.
     * </p>
     */
    private static class SerializationCheckerObjectOutputStream extends ObjectOutputStream {
        private final OutputStream outputStream;

        private final ObjectOutputStream oos;

        private SerializationCheckerObjectOutputStream(OutputStream outputStream) throws IOException {
            this.outputStream = outputStream;
            oos = new ObjectOutputStream(outputStream);
        }

        @Override
        protected final void writeObjectOverride(Object obj) throws IOException {
            try {
                oos.writeObject(obj);
            } catch (NotSerializableException nsx) {
                if (CheckingObjectOutputStream.isAvailable()) {
                    try {
                        // trigger serialization again, but this time gather some more info
                        CheckingObjectOutputStream checkingObjectOutputStream = new CheckingObjectOutputStream(
                                outputStream, new ObjectSerializationChecker(nsx));
                        checkingObjectOutputStream.writeObject(obj);
                    } catch (Exception x) {
                        if (x instanceof CheckingObjectOutputStream.ObjectCheckException) {
                            throw (CheckingObjectOutputStream.ObjectCheckException) x;
                        } else {
                            x.initCause(nsx);
                            throw new WicketRuntimeException(
                                    "A problem occurred while trying to collect debug information about not serializable object",
                                    x);
                        }
                    }

                    // if we get here, we didn't fail, while we should
                    throw nsx;
                }
                throw nsx;
            } catch (Exception e) {
                log.error("error writing object " + obj + ": " + e.getMessage(), e);
                throw new WicketRuntimeException(e);
            }
        }

        @Override
        public void flush() throws IOException {
            oos.flush();
        }

        @Override
        public void close() throws IOException {
            oos.close();
        }
    }
}