Package org.apache.wicket.util.lang

Source Code of org.apache.wicket.util.lang.WicketObjects$ReplaceObjectOutputStream

/*
* 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.util.lang;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.HashMap;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.ThreadContext;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.application.IClassResolver;
import org.apache.wicket.settings.IApplicationSettings;
import org.apache.wicket.util.io.ByteCountingOutputStream;
import org.apache.wicket.util.io.IOUtils;
import org.apache.wicket.util.io.IObjectStreamFactory;
import org.apache.wicket.util.io.IObjectStreamFactory.DefaultObjectStreamFactory;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Object (de)serialization utilities.
*/
public class WicketObjects
{
  /** log. */
  private static final Logger log = LoggerFactory.getLogger(WicketObjects.class);

  private WicketObjects()
  {
  }

  /**
   * @param <T>
   *            class type
   * @param className
   *            Class to resolve
   * @return Resolved class
   * @throws ClassNotFoundException
   */
  @SuppressWarnings("unchecked")
  public static <T> Class<T> resolveClass(final String className)
  {
    Class<T> resolved = null;
    try
    {
      if (Application.exists())
      {
        resolved = (Class<T>)Application.get()
          .getApplicationSettings()
          .getClassResolver()
          .resolveClass(className);
      }

      if (resolved == null)
      {
        resolved = (Class<T>)Class.forName(className, false, Thread.currentThread()
          .getContextClassLoader());
      }
    }
    catch (ClassNotFoundException cnfx)
    {
      log.warn("Could not resolve class [" + className + "]", cnfx);
    }
    return resolved;
  }

  /**
   * Interface that enables users to plugin the way object sizes are calculated with Wicket.
   */
  public static interface IObjectSizeOfStrategy
  {
    /**
     * Computes the size of an object. This typically is an estimation, not an absolute accurate
     * size.
     *
     * @param object
     *            The serializable object to compute size of
     * @return The size of the object in bytes.
     */
    long sizeOf(Serializable object);
  }

  /**
   * {@link IObjectSizeOfStrategy} that works by serializing the object to an instance of
   * {@link ByteCountingOutputStream}, which records the number of bytes written to it. Hence,
   * this gives the size of the object as it would be serialized,including all the overhead of
   * writing class headers etc. Not very accurate (the real memory consumption should be lower)
   * but the best we can do in a cheap way pre JDK 5.
   */
  public static final class SerializingObjectSizeOfStrategy implements IObjectSizeOfStrategy
  {
    /**
     * @see org.apache.wicket.util.lang.WicketObjects.IObjectSizeOfStrategy#sizeOf(java.io.Serializable)
     */
    public long sizeOf(Serializable object)
    {
      if (object == null)
      {
        return 0;
      }
      try
      {
        final ByteCountingOutputStream out = new ByteCountingOutputStream();
        new ObjectOutputStream(out).writeObject(object);
        out.close();
        return out.size();
      }
      catch (IOException e)
      {
        if (log.isWarnEnabled())
        {
          log.warn("Unable to determine object size: " + object.toString(), e);
        }
        return -1;
      }
    }

  }

  private static final class ReplaceObjectInputStream extends ObjectInputStream
  {
    private final ClassLoader classloader;
    private final HashMap<String, Component> replacedComponents;

    private ReplaceObjectInputStream(InputStream in,
      HashMap<String, Component> replacedComponents, ClassLoader classloader)
      throws IOException
    {
      super(in);
      this.replacedComponents = replacedComponents;
      this.classloader = classloader;
      enableResolveObject(true);
    }

    // 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
    {
      String className = desc.getName();

      try
      {
        return Class.forName(className, true, classloader);
      }
      catch (ClassNotFoundException ex1)
      {
        // ignore this exception.
        log.debug("Class not found by using objects own classloader, trying the IClassResolver");
      }

      Application application = Application.get();
      IApplicationSettings applicationSettings = application.getApplicationSettings();
      IClassResolver classResolver = applicationSettings.getClassResolver();

      Class<?> candidate = null;
      try
      {
        candidate = classResolver.resolveClass(className);
        if (candidate == null)
        {
          candidate = super.resolveClass(desc);
        }
      }
      catch (WicketRuntimeException ex)
      {
        if (ex.getCause() instanceof ClassNotFoundException)
        {
          throw (ClassNotFoundException)ex.getCause();
        }
      }
      return candidate;
    }

    @Override
    protected Object resolveObject(Object obj) throws IOException
    {
      Object replaced = replacedComponents.get(obj);
      if (replaced != null)
      {
        return replaced;
      }
      return super.resolveObject(obj);
    }
  }

  private static final class ReplaceObjectOutputStream extends ObjectOutputStream
  {
    private final HashMap<String, Component> replacedComponents;

    private ReplaceObjectOutputStream(OutputStream out,
      HashMap<String, Component> replacedComponents) throws IOException
    {
      super(out);
      this.replacedComponents = replacedComponents;
      enableReplaceObject(true);
    }

    @Override
    protected Object replaceObject(Object obj) throws IOException
    {
      if (obj instanceof Component)
      {
        final Component component = (Component)obj;
        String name = component.getPath();
        replacedComponents.put(name, component);
        return name;
      }
      return super.replaceObject(obj);
    }
  }

  /**
   * De-serializes an object from a byte array.
   *
   * @param data
   *            The serialized object
   * @return The object
   */
  public static Object byteArrayToObject(final byte[] data)
  {
    ThreadContext old = ThreadContext.get(false);
    try
    {
      final ByteArrayInputStream in = new ByteArrayInputStream(data);
      ObjectInputStream ois = null;
      try
      {
        ois = objectStreamFactory.newObjectInputStream(in);
        String applicationName = (String)ois.readObject();
        if (applicationName != null && !Application.exists())
        {
          Application app = Application.get(applicationName);
          if (app != null)
          {
            ThreadContext.setApplication(app);
          }
        }
        return ois.readObject();
      }
      finally
      {
        try
        {
          IOUtils.close(ois);
        }
        finally
        {
          in.close();
        }
      }
    }
    catch (ClassNotFoundException e)
    {
      throw new RuntimeException("Could not deserialize object using `" +
        objectStreamFactory.getClass().getName() + "` object factory", e);
    }
    catch (IOException e)
    {
      throw new RuntimeException("Could not deserialize object using `" +
        objectStreamFactory.getClass().getName() + "` object factory", e);
    }
    finally
    {
      ThreadContext.restore(old);
    }
  }

  /**
   * Makes a deep clone of an object by serializing and deserializing it. The object must be fully
   * serializable to be cloned. This method will not clone wicket Components, it will just reuse
   * those instances so that the complete component tree is not copied over only the model data.
   *
   * @param object
   *            The object to clone
   * @return A deep copy of the object
   */
  public static Object cloneModel(final Object object)
  {
    if (object == null)
    {
      return null;
    }
    else
    {
      try
      {
        final ByteArrayOutputStream out = new ByteArrayOutputStream(256);
        final HashMap<String, Component> replacedObjects = Generics.newHashMap();
        ObjectOutputStream oos = new ReplaceObjectOutputStream(out, replacedObjects);
        oos.writeObject(object);
        ObjectInputStream ois = new ReplaceObjectInputStream(new ByteArrayInputStream(
          out.toByteArray()), replacedObjects, object.getClass().getClassLoader());
        return ois.readObject();
      }
      catch (ClassNotFoundException e)
      {
        throw new WicketRuntimeException("Internal error cloning object", e);
      }
      catch (IOException e)
      {
        throw new WicketRuntimeException("Internal error cloning object", e);
      }
    }
  }

  /**
   * The default object stream factory to use. Keep this as a static here opposed to in
   * Application, as the Application most likely isn't available in the threads we'll be using
   * this with.
   */
  private static IObjectStreamFactory objectStreamFactory = new IObjectStreamFactory.DefaultObjectStreamFactory();

  /**
   * Strategy for calculating sizes of objects. Note: I didn't make this an application setting as
   * we have enough of those already, and the typical way this probably would be used is that
   * install a different one according to the JDK version used, so varying them between
   * applications doesn't make a lot of sense.
   */
  private static IObjectSizeOfStrategy objectSizeOfStrategy = new SerializingObjectSizeOfStrategy();

  /**
   * Makes a deep clone of an object by serializing and deserializing it. The object must be fully
   * serializable to be cloned. No extra debug info is gathered.
   *
   * @param object
   *            The object to clone
   * @return A deep copy of the object
   * @see #cloneModel(Object)
   */
  public static Object cloneObject(final Object object)
  {
    if (object == null)
    {
      return null;
    }
    else
    {
      try
      {
        final ByteArrayOutputStream out = new ByteArrayOutputStream(256);
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(object);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(
          out.toByteArray()))
        {
          // 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
          {
            String className = desc.getName();

            try
            {
              return Class.forName(className, true, object.getClass()
                .getClassLoader());
            }
            catch (ClassNotFoundException ex1)
            {
              // ignore this exception.
              log.debug("Class not found by using objects own classloader, trying the IClassResolver");
            }


            Application application = Application.get();
            IApplicationSettings applicationSettings = application.getApplicationSettings();
            IClassResolver classResolver = applicationSettings.getClassResolver();

            Class<?> candidate = null;
            try
            {
              candidate = classResolver.resolveClass(className);
              if (candidate == null)
              {
                candidate = super.resolveClass(desc);
              }
            }
            catch (WicketRuntimeException ex)
            {
              if (ex.getCause() instanceof ClassNotFoundException)
              {
                throw (ClassNotFoundException)ex.getCause();
              }
            }
            return candidate;
          }
        };
        return ois.readObject();
      }
      catch (ClassNotFoundException e)
      {
        throw new WicketRuntimeException("Internal error cloning object", e);
      }
      catch (IOException e)
      {
        throw new WicketRuntimeException("Internal error cloning object", e);
      }
    }
  }

  /**
   * Creates a new instance using the current application's class resolver. Returns null if
   * className is null.
   *
   * @param className
   *            The full class name
   * @return The new object instance
   */
  public static Object newInstance(final String className)
  {
    if (!Strings.isEmpty(className))
    {
      try
      {
        Class<?> c = WicketObjects.resolveClass(className);
        return c.newInstance();
      }
      catch (Exception e)
      {
        throw new WicketRuntimeException("Unable to create " + className, e);
      }
    }
    return null;
  }

  /**
   * Serializes an object into a byte array.
   *
   * @param object
   *            The object
   *
   * @param applicationName
   *            The name of application - required when serialization and deserialisation happen
   *            outside thread in which application thread local is set
   *
   * @return The serialized object
   */
  public static byte[] objectToByteArray(final Object object, String applicationName)
  {
    try
    {
      final ByteArrayOutputStream out = new ByteArrayOutputStream();
      ObjectOutputStream oos = null;
      try
      {
        oos = objectStreamFactory.newObjectOutputStream(out);
        oos.writeObject(applicationName);
        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;
  }

  /**
   * Serializes an object into a byte array.
   *
   * @param object
   *            The object
   * @return The serialized object
   */
  public static byte[] objectToByteArray(final Object object)
  {
    try
    {
      final ByteArrayOutputStream out = new ByteArrayOutputStream();
      ObjectOutputStream oos = null;
      try
      {
        oos = objectStreamFactory.newObjectOutputStream(out);
        if (Application.exists())
        {
          oos.writeObject(Application.get().getApplicationKey());
        }
        else
        {
          oos.writeObject(null);
        }
        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;
  }

  /**
   * Sets the strategy for determining the sizes of objects.
   *
   * @param objectSizeOfStrategy
   *            the strategy. Pass null to reset to the default.
   */
  public static void setObjectSizeOfStrategy(IObjectSizeOfStrategy objectSizeOfStrategy)
  {
    if (objectSizeOfStrategy == null)
    {
      WicketObjects.objectSizeOfStrategy = new SerializingObjectSizeOfStrategy();
    }
    else
    {
      WicketObjects.objectSizeOfStrategy = objectSizeOfStrategy;
    }
    log.info("using " + objectSizeOfStrategy + " for calculating object sizes");
  }

  /**
   * Configure this utility class to use the provided {@link IObjectStreamFactory} instance.
   *
   * @param objectStreamFactory
   *            The factory instance to use. If you pass in null, the
   *            {@link DefaultObjectStreamFactory default} will be set (again). Pass null to reset
   *            to the default.
   */
  public static void setObjectStreamFactory(IObjectStreamFactory objectStreamFactory)
  {
    if (objectStreamFactory == null)
    {
      WicketObjects.objectStreamFactory = new IObjectStreamFactory.DefaultObjectStreamFactory();
    }
    else
    {
      WicketObjects.objectStreamFactory = objectStreamFactory;
    }
    log.info("using " + WicketObjects.objectStreamFactory + " for creating object streams");
  }

  /**
   * Computes the size of an object. Note that this is an estimation, never an absolute accurate
   * size.
   *
   * @param object
   *            Object to compute size of
   * @return The size of the object in bytes
   */
  public static long sizeof(final Serializable object)
  {
    return objectSizeOfStrategy.sizeOf(object);
  }
}
TOP

Related Classes of org.apache.wicket.util.lang.WicketObjects$ReplaceObjectOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.