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