Why do you throw WicketNotSerializableException when the model is still
detached?

On Fri, Sep 5, 2008 at 11:11 AM, Kaspar Fischer <[EMAIL PROTECTED]>wrote:

> For the sake of completeness, here is the solution I am currently using. It
> uses,
> as suggested by Martijn, a custom request cycle and a modified version of
> SerializableChecker. You have to install the custom request cycle in your
> application
> using
>
>  @Override
>  public RequestCycle newRequestCycle(Request request, Response response)
>  {
>    return new CustomRequestCycle(this, (WebRequest) request, (WebResponse)
> response);
>  }
>
> Hope this helps others, too!
> Kaspar
>
> // ***** FILE: CustomRequestCycle.java *****
> import java.io.NotSerializableException;
>
> import org.apache.wicket.Page;
> import org.apache.wicket.Response;
> import org.apache.wicket.protocol.http.WebApplication;
> import org.apache.wicket.protocol.http.WebRequest;
> import org.apache.wicket.protocol.http.WebRequestCycle;
> import org.apache.wicket.request.target.component.IPageRequestTarget;
> import org.slf4j.Logger;
> import org.slf4j.LoggerFactory;
>
> /**
>  * A custom request cycle that checks, when in development mode, that all
> models of a page are
>  * detached. Currently, only model that are instances of
> LoadableDetachableModel (including
>  * subclasses) are checked.
>  */
> public class CustomRequestCycle extends WebRequestCycle
> {
>  /** Logging object */
>  private static final Logger log =
> LoggerFactory.getLogger(WebRequestCycle.class);
>
>  public CustomRequestCycle(WebApplication application, WebRequest request,
> Response response)
>  {
>    super(application, request, response);
>  }
>
>  @Override
>  protected void onEndRequest()
>  {
>    super.onEndRequest();
>
>    if
> (WebApplication.DEVELOPMENT.equalsIgnoreCase(WebApplication.get().getConfigurationType()))
>    {
>      Page requestPage = getRequest().getPage();
>      testDetachedObjects(requestPage);
>
>      if (getRequestTarget() instanceof IPageRequestTarget)
>      {
>        Page responsePage = ((IPageRequestTarget)
> getRequestTarget()).getPage();
>
>        if (responsePage != requestPage)
>        {
>          testDetachedObjects(responsePage);
>        }
>      }
>    }
>  }
>
>  private void testDetachedObjects(final Page page)
>  {
>    if (page == null)
>    {
>      return;
>    }
>
>    try
>    {
>      NotSerializableException exception = new NotSerializableException(
>          "Model is not detached when attempting to serialize!");
>      DetachedChecker checker = new DetachedChecker(exception);
>      checker.writeObject(page);
>    }
>    catch (Exception ex)
>    {
>      log.error("Couldn't test/serialize the Page: " + page + ", error: " +
> ex);
>    }
>  }
> }
>
>
> // ***** FILE: DetachedChecker.java *****
> import java.io.Externalizable;
> import java.io.IOException;
> import java.io.NotSerializableException;
> import java.io.ObjectOutput;
> import java.io.ObjectOutputStream;
> import java.io.ObjectStreamClass;
> import java.io.ObjectStreamField;
> import java.io.OutputStream;
> import java.io.Serializable;
> import java.lang.reflect.Field;
> import java.lang.reflect.InvocationTargetException;
> import java.lang.reflect.Method;
> import java.lang.reflect.Proxy;
> import java.util.Date;
> import java.util.IdentityHashMap;
> import java.util.Iterator;
> import java.util.LinkedList;
> import java.util.Map;
>
> import org.apache.wicket.Component;
> import org.apache.wicket.WicketRuntimeException;
> import org.apache.wicket.model.LoadableDetachableModel;
> import org.apache.wicket.util.lang.Generics;
> import org.slf4j.Logger;
> import org.slf4j.LoggerFactory;
>
> /**
>  * This is taken from Wicket SerializableChecker.java (SVN r687197) and
> customized slightly
>  * (see comments containing "KF"). See the latter file for all details,
> including terms of
>  * use. Notice that this does not replace SerializableChecker; the latter
> is still run.
>  */
> public final class DetachedChecker extends ObjectOutputStream
> {
>  /**
>   * Exception that is thrown when a non-serializable object was found.
>   */
>  public static final class WicketNotSerializableException extends
> WicketRuntimeException
>  {
>    private static final long serialVersionUID = 1L;
>
>    WicketNotSerializableException(String message, Throwable cause)
>    {
>      super(message, cause);
>    }
>  }
>
>  /**
>   * Does absolutely nothing.
>   */
>  private static class NoopOutputStream extends OutputStream
>  {
>    @Override
>    public void close()
>    {
>    }
>
>    @Override
>    public void flush()
>    {
>    }
>
>    @Override
>    public void write(byte[] b)
>    {
>    }
>
>    @Override
>    public void write(byte[] b, int i, int l)
>    {
>    }
>
>    @Override
>    public void write(int b)
>    {
>    }
>  }
>
>  private static abstract class ObjectOutputAdaptor implements ObjectOutput
>  {
>
>    public void close() throws IOException
>    {
>    }
>
>    public void flush() throws IOException
>    {
>    }
>
>    public void write(byte[] b) throws IOException
>    {
>    }
>
>    public void write(byte[] b, int off, int len) throws IOException
>    {
>    }
>
>    public void write(int b) throws IOException
>    {
>    }
>
>    public void writeBoolean(boolean v) throws IOException
>    {
>    }
>
>    public void writeByte(int v) throws IOException
>    {
>    }
>
>    public void writeBytes(String s) throws IOException
>    {
>    }
>
>    public void writeChar(int v) throws IOException
>    {
>    }
>
>    public void writeChars(String s) throws IOException
>    {
>    }
>
>    public void writeDouble(double v) throws IOException
>    {
>    }
>
>    public void writeFloat(float v) throws IOException
>    {
>    }
>
>    public void writeInt(int v) throws IOException
>    {
>    }
>
>    public void writeLong(long v) throws IOException
>    {
>    }
>
>    public void writeShort(int v) throws IOException
>    {
>    }
>
>    public void writeUTF(String str) throws IOException
>    {
>    }
>  }
>
>  /** Holds information about the field and the resulting object being
> traced. */
>  private static final class TraceSlot
>  {
>    private final String fieldDescription;
>
>    private final Object object;
>
>    TraceSlot(Object object, String fieldDescription)
>    {
>      super();
>      this.object = object;
>      this.fieldDescription = fieldDescription;
>    }
>
>    @Override
>    public String toString()
>    {
>      return object.getClass() + " - " + fieldDescription;
>    }
>  }
>
>  private static final NoopOutputStream DUMMY_OUTPUT_STREAM = new
> NoopOutputStream();
>
>  /** log. */
>  private static final Logger log =
> LoggerFactory.getLogger(DetachedChecker.class);
>
>  /** Whether we can execute the tests. If false, check will just return. */
>  private static boolean available = true;
>
>  // this hack - accessing the serialization API through introspection - is
>  // the only way to use Java serialization for our purposes without writing
>  // the whole thing from scratch (and even then, it would be limited). This
>  // way of working is of course fragile for internal API changes, but as we
>  // do an extra check on availability and we report when we can't use this
>  // introspection fu, we'll find out soon enough and clients on this class
>  // can fall back on Java's default exception for serialization errors
> (which
>  // sucks and is the main reason for this attempt).
>  private static final Method LOOKUP_METHOD;
>
>  private static final Method GET_CLASS_DATA_LAYOUT_METHOD;
>
>  private static final Method GET_NUM_OBJ_FIELDS_METHOD;
>
>  private static final Method GET_OBJ_FIELD_VALUES_METHOD;
>
>  private static final Method GET_FIELD_METHOD;
>
>  private static final Method HAS_WRITE_REPLACE_METHOD_METHOD;
>
>  private static final Method INVOKE_WRITE_REPLACE_METHOD;
>
>  static
>  {
>    try
>    {
>      LOOKUP_METHOD = ObjectStreamClass.class.getDeclaredMethod("lookup",
> new Class[] {
>          Class.class, Boolean.TYPE
>      });
>      LOOKUP_METHOD.setAccessible(true);
>
>      GET_CLASS_DATA_LAYOUT_METHOD =
> ObjectStreamClass.class.getDeclaredMethod("getClassDataLayout", (Class[])
> null);
>      GET_CLASS_DATA_LAYOUT_METHOD.setAccessible(true);
>
>      GET_NUM_OBJ_FIELDS_METHOD =
> ObjectStreamClass.class.getDeclaredMethod("getNumObjFields", (Class[])
> null);
>      GET_NUM_OBJ_FIELDS_METHOD.setAccessible(true);
>
>      GET_OBJ_FIELD_VALUES_METHOD =
> ObjectStreamClass.class.getDeclaredMethod("getObjFieldValues", new Class[] {
>          Object.class, Object[].class
>      });
>      GET_OBJ_FIELD_VALUES_METHOD.setAccessible(true);
>
>      GET_FIELD_METHOD =
> ObjectStreamField.class.getDeclaredMethod("getField", (Class[]) null);
>      GET_FIELD_METHOD.setAccessible(true);
>
>      HAS_WRITE_REPLACE_METHOD_METHOD =
> ObjectStreamClass.class.getDeclaredMethod("hasWriteReplaceMethod",
>          (Class[]) null);
>      HAS_WRITE_REPLACE_METHOD_METHOD.setAccessible(true);
>
>      INVOKE_WRITE_REPLACE_METHOD =
> ObjectStreamClass.class.getDeclaredMethod("invokeWriteReplace", new Class[]
> {
>        Object.class
>      });
>      INVOKE_WRITE_REPLACE_METHOD.setAccessible(true);
>    }
>    catch (SecurityException e)
>    {
>      available = false;
>      throw new RuntimeException(e);
>    }
>    catch (NoSuchMethodException e)
>    {
>      available = false;
>      throw new RuntimeException(e);
>    }
>  }
>
>  /**
>   * Gets whether we can execute the tests. If false, calling [EMAIL PROTECTED]
> #check(Object)} will just
>   * return and you are advised to rely on the [EMAIL PROTECTED]
> NotSerializableException}. Clients are
>   * advised to call this method prior to calling the check method.
>   *
>   * @return whether security settings and underlying API etc allow for
> accessing the serialization
>   *         API using introspection
>   */
>  public static boolean isAvailable()
>  {
>    return available;
>  }
>
>  /** object stack that with the trace path. */
>  private final LinkedList<TraceSlot> traceStack = new
> LinkedList<TraceSlot>();
>
>  /** set for checking circular references. */
>  private final Map<Object, Object> checked = new IdentityHashMap<Object,
> Object>();
>
>  /** string stack with current names pushed. */
>  private final LinkedList<String> nameStack = new LinkedList<String>();
>
>  /** root object being analyzed. */
>  private Object root;
>
>  /** cache for classes - writeObject methods. */
>  private final Map<Class<?>, Object> writeObjectMethodCache =
> Generics.newHashMap();
>
>  /** current simple field name. */
>  private String simpleName = "";
>
>  /** current full field description. */
>  private String fieldDescription;
>
>  /** Exception that should be set as the cause when throwing a new
> exception. */
>  private final NotSerializableException exception;
>
>  /**
>   * Construct.
>   *
>   * @param exception
>   *          exception that should be set as the cause when throwing a new
> exception
>   *
>   * @throws IOException
>   */
>  public DetachedChecker(NotSerializableException exception) throws
> IOException
>  {
>    this.exception = exception;
>  }
>
>  /**
>   * @see java.io.ObjectOutputStream#reset()
>   */
>  @Override
>  public void reset() throws IOException
>  {
>    root = null;
>    checked.clear();
>    fieldDescription = null;
>    simpleName = null;
>    traceStack.clear();
>    nameStack.clear();
>    writeObjectMethodCache.clear();
>  }
>
>  private void check(Object obj)
>  {
>    if (obj == null)
>    {
>      return;
>    }
>
>    Class<?> cls = obj.getClass();
>    nameStack.add(simpleName);
>    traceStack.add(new TraceSlot(obj, fieldDescription));
>
>    if (!(obj instanceof Serializable) && (!Proxy.isProxyClass(cls)))
>    {
>      throw new
> WicketNotSerializableException(toPrettyPrintedStack(obj.getClass().getName()),
> exception);
>    }
>
>    // BEGIN KF
>    if (obj instanceof LoadableDetachableModel)
>    {
>      LoadableDetachableModel model = (LoadableDetachableModel) obj;
>      if (model.isAttached())
>      {
>        Object value = model.getObject();
>        throw new
> WicketNotSerializableException(toPrettyPrintedStack(obj.getClass().getName())
> + "\nmodel object: "
>            + value, exception);
>      }
>    }
>    // END KF
>
>    ObjectStreamClass desc;
>    for (;;)
>    {
>      try
>      {
>        desc = (ObjectStreamClass) LOOKUP_METHOD.invoke(null, new Object[] {
>            cls, Boolean.TRUE
>        });
>        Class<?> repCl;
>        if (!((Boolean) HAS_WRITE_REPLACE_METHOD_METHOD.invoke(desc,
> (Object[]) null)).booleanValue()
>            || (obj = INVOKE_WRITE_REPLACE_METHOD.invoke(desc, new Object[]
> {
>              obj
>            })) == null || (repCl = obj.getClass()) == cls)
>        {
>          break;
>        }
>        cls = repCl;
>      }
>      catch (IllegalAccessException e)
>      {
>        throw new RuntimeException(e);
>      }
>      catch (InvocationTargetException e)
>      {
>        throw new RuntimeException(e);
>      }
>    }
>
>    if (cls.isPrimitive())
>    {
>      // skip
>    }
>    else if (cls.isArray())
>    {
>      checked.put(obj, null);
>      Class<?> ccl = cls.getComponentType();
>      if (!(ccl.isPrimitive()))
>      {
>        Object[] objs = (Object[]) obj;
>        for (int i = 0; i < objs.length; i++)
>        {
>          String arrayPos = "[" + i + "]";
>          simpleName = arrayPos;
>          fieldDescription += arrayPos;
>          check(objs[i]);
>        }
>      }
>    }
>    else if (obj instanceof Externalizable && (!Proxy.isProxyClass(cls)))
>    {
>      Externalizable extObj = (Externalizable) obj;
>      try
>      {
>        extObj.writeExternal(new ObjectOutputAdaptor()
>        {
>          private int count = 0;
>
>          public void writeObject(Object streamObj) throws IOException
>          {
>            // Check for circular reference.
>            if (checked.containsKey(streamObj))
>            {
>              return;
>            }
>
>            checked.put(streamObj, null);
>            String arrayPos = "[write:" + count++ + "]";
>            simpleName = arrayPos;
>            fieldDescription += arrayPos;
>
>            check(streamObj);
>          }
>        });
>      }
>      catch (Exception e)
>      {
>        if (e instanceof WicketNotSerializableException)
>        {
>          throw (WicketNotSerializableException) e;
>        }
>        log.warn("error delegating to Externalizable : " + e.getMessage() +
> ", path: " + currentPath());
>      }
>    }
>    else
>    {
>      Method writeObjectMethod = null;
>      Object o = writeObjectMethodCache.get(cls);
>      if (o != null)
>      {
>        if (o instanceof Method)
>        {
>          writeObjectMethod = (Method) o;
>        }
>      }
>      else
>      {
>        try
>        {
>          writeObjectMethod = cls.getDeclaredMethod("writeObject", new
> Class[] {
>            java.io.ObjectOutputStream.class
>          });
>        }
>        catch (SecurityException e)
>        {
>          // we can't access/ set accessible to true
>          writeObjectMethodCache.put(cls, Boolean.FALSE);
>        }
>        catch (NoSuchMethodException e)
>        {
>          // cls doesn't have that method
>          writeObjectMethodCache.put(cls, Boolean.FALSE);
>        }
>      }
>
>      final Object original = obj;
>      if (writeObjectMethod != null)
>      {
>        class InterceptingObjectOutputStream extends ObjectOutputStream
>        {
>          private int counter;
>
>          InterceptingObjectOutputStream() throws IOException
>          {
>            super(DUMMY_OUTPUT_STREAM);
>            enableReplaceObject(true);
>          }
>
>          @Override
>          protected Object replaceObject(Object streamObj) throws
> IOException
>          {
>            if (streamObj == original)
>            {
>              return streamObj;
>            }
>
>            counter++;
>            // Check for circular reference.
>            if (checked.containsKey(streamObj))
>            {
>              return null;
>            }
>
>            checked.put(original, null);
>            String arrayPos = "[write:" + counter + "]";
>            simpleName = arrayPos;
>            fieldDescription += arrayPos;
>            check(streamObj);
>            return streamObj;
>          }
>        }
>        try
>        {
>          InterceptingObjectOutputStream ioos = new
> InterceptingObjectOutputStream();
>          ioos.writeObject(obj);
>        }
>        catch (Exception e)
>        {
>          if (e instanceof WicketNotSerializableException)
>          {
>            throw (WicketNotSerializableException) e;
>          }
>          log.warn("error delegating to writeObject : " + e.getMessage() +
> ", path: " + currentPath());
>        }
>      }
>      else
>      {
>        Object[] slots;
>        try
>        {
>          slots = (Object[]) GET_CLASS_DATA_LAYOUT_METHOD.invoke(desc,
> (Object[]) null);
>        }
>        catch (Exception e)
>        {
>          throw new RuntimeException(e);
>        }
>        for (int i = 0; i < slots.length; i++)
>        {
>          ObjectStreamClass slotDesc;
>          try
>          {
>            Field descField = slots[i].getClass().getDeclaredField("desc");
>            descField.setAccessible(true);
>            slotDesc = (ObjectStreamClass) descField.get(slots[i]);
>          }
>          catch (Exception e)
>          {
>            throw new RuntimeException(e);
>          }
>          checked.put(obj, null);
>          checkFields(obj, slotDesc);
>        }
>      }
>    }
>
>    traceStack.removeLast();
>    nameStack.removeLast();
>  }
>
>  private void checkFields(Object obj, ObjectStreamClass desc)
>  {
>    int numFields;
>    try
>    {
>      numFields = ((Integer) GET_NUM_OBJ_FIELDS_METHOD.invoke(desc,
> (Object[]) null)).intValue();
>    }
>    catch (IllegalAccessException e)
>    {
>      throw new RuntimeException(e);
>    }
>    catch (InvocationTargetException e)
>    {
>      throw new RuntimeException(e);
>    }
>
>    if (numFields > 0)
>    {
>      int numPrimFields;
>      ObjectStreamField[] fields = desc.getFields();
>      Object[] objVals = new Object[numFields];
>      numPrimFields = fields.length - objVals.length;
>      try
>      {
>        GET_OBJ_FIELD_VALUES_METHOD.invoke(desc, new Object[] {
>            obj, objVals
>        });
>      }
>      catch (IllegalAccessException e)
>      {
>        throw new RuntimeException(e);
>      }
>      catch (InvocationTargetException e)
>      {
>        throw new RuntimeException(e);
>      }
>      for (int i = 0; i < objVals.length; i++)
>      {
>        if (objVals[i] instanceof String || objVals[i] instanceof Number ||
> objVals[i] instanceof Date
>            || objVals[i] instanceof Boolean || objVals[i] instanceof Class)
>        {
>          // filter out common cases
>          continue;
>        }
>
>        // Check for circular reference.
>        if (checked.containsKey(objVals[i]))
>        {
>          continue;
>        }
>
>        ObjectStreamField fieldDesc = fields[numPrimFields + i];
>        Field field;
>        try
>        {
>          field = (Field) GET_FIELD_METHOD.invoke(fieldDesc, (Object[])
> null);
>        }
>        catch (IllegalAccessException e)
>        {
>          throw new RuntimeException(e);
>        }
>        catch (InvocationTargetException e)
>        {
>          throw new RuntimeException(e);
>        }
>
>        String fieldName = field.getName();
>        simpleName = field.getName();
>        fieldDescription = field.toString();
>        check(objVals[i]);
>      }
>    }
>  }
>
>  /**
>   * @return name from root to current node concatenated with slashes
>   */
>  private StringBuffer currentPath()
>  {
>    StringBuffer b = new StringBuffer();
>    for (Iterator<String> it = nameStack.iterator(); it.hasNext();)
>    {
>      b.append(it.next());
>      if (it.hasNext())
>      {
>        b.append('/');
>      }
>    }
>    return b;
>  }
>
>  /**
>   * Dump with indentation.
>   *
>   * @param type
>   *          the type that couldn't be serialized
>   * @return A very pretty dump
>   */
>  private final String toPrettyPrintedStack(String type)
>  {
>    StringBuffer result = new StringBuffer();
>    StringBuffer spaces = new StringBuffer();
>    result.append("Unable to serialize class: ");
>    result.append(type);
>    result.append("\nField hierarchy is:");
>    for (Iterator<TraceSlot> i = traceStack.listIterator(); i.hasNext();)
>    {
>      spaces.append("  ");
>      TraceSlot slot = i.next();
>      result.append("\n").append(spaces).append(slot.fieldDescription);
>      result.append(" [class=").append(slot.object.getClass().getName());
>      if (slot.object instanceof Component)
>      {
>        Component component = (Component) slot.object;
>        result.append(", path=").append(component.getPath());
>      }
>      result.append("]");
>      result.append(" {object:" + slot.object + "}");
>    }
>    result.append(" <----- model that is not detached"); // KF
>    return result.toString();
>  }
>
>  /**
>   * @see java.io.ObjectOutputStream#writeObjectOverride(java.lang.Object)
>   */
>  @Override
>  protected final void writeObjectOverride(Object obj) throws IOException
>  {
>    if (!available)
>    {
>      return;
>    }
>    root = obj;
>    if (fieldDescription == null)
>    {
>      fieldDescription = (root instanceof Component) ? ((Component)
> root).getPath() : "";
>    }
>
>    check(root);
>  }
> }
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
>
>


-- 
Eyal Golan
[EMAIL PROTECTED]

Visit: http://jvdrums.sourceforge.net/
LinkedIn: http://www.linkedin.com/in/egolan74

P Save a tree. Please don't print this e-mail unless it's really necessary

Reply via email to