You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@wicket.apache.org by Kaspar Fischer <fi...@inf.ethz.ch> on 2008/09/05 10:11:46 UTC

Re: Assert that all models are detached at the end of the request?

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 {@link  
#check(Object)} will just
    * return and you are advised to rely on the {@link  
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: users-unsubscribe@wicket.apache.org
For additional commands, e-mail: users-help@wicket.apache.org


Re: Assert that all models are detached at the end of the request?

Posted by Kaspar Fischer <fi...@inf.ethz.ch>.
On 09.09.2008, at 08:14, Eyal Golan wrote:

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

You're right, it does not make sense. I simply didn't bother because I  
wanted to
change as little as possible in the file so that when  
SerializableChecker is
updated, I have little to do...

> On Fri, Sep 5, 2008 at 11:11 AM, Kaspar Fischer  
> <fi...@inf.ethz.ch>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 {@link
>> #check(Object)} will just
>>  * return and you are advised to rely on the {@link
>> 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: users-unsubscribe@wicket.apache.org
>> For additional commands, e-mail: users-help@wicket.apache.org
>>
>>
>
>
> -- 
> Eyal Golan
> egolan74@gmail.com
>
> 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


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@wicket.apache.org
For additional commands, e-mail: users-help@wicket.apache.org


Re: Assert that all models are detached at the end of the request?

Posted by Eyal Golan <eg...@gmail.com>.
Why do you throw WicketNotSerializableException when the model is still
detached?

On Fri, Sep 5, 2008 at 11:11 AM, Kaspar Fischer <fi...@inf.ethz.ch>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 {@link
> #check(Object)} will just
>   * return and you are advised to rely on the {@link
> 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: users-unsubscribe@wicket.apache.org
> For additional commands, e-mail: users-help@wicket.apache.org
>
>


-- 
Eyal Golan
egolan74@gmail.com

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