Updated Branches:
  refs/heads/wicket-1.5.x 49dc17ea9 -> 8815e7c61

WICKET-4812 Make SerializationChecker easier for extending so custom checks can 
be added to it


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/8815e7c6
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/8815e7c6
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/8815e7c6

Branch: refs/heads/wicket-1.5.x
Commit: 8815e7c61fe197703c4a782e224c0565495f737b
Parents: 49dc17e
Author: Martin Tzvetanov Grigorov <mgrigo...@apache.org>
Authored: Tue Oct 16 10:53:18 2012 +0200
Committer: Martin Tzvetanov Grigorov <mgrigo...@apache.org>
Committed: Tue Oct 16 10:53:18 2012 +0200

----------------------------------------------------------------------
 .../apache/wicket/util/io/SerializableChecker.java |  702 ++-------------
 .../objects/checker/AbstractObjectChecker.java     |   84 ++
 .../util/objects/checker/IObjectChecker.java       |  126 +++
 .../objects/checker/NotDetachedModelChecker.java   |   69 ++
 .../wicket/util/objects/checker/ObjectChecker.java |  709 +++++++++++++++
 .../objects/checker/OrphanComponentChecker.java    |   74 ++
 .../wicket/util/io/SerializableCheckerTest.java    |    8 +-
 7 files changed, 1119 insertions(+), 653 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/8815e7c6/wicket-core/src/main/java/org/apache/wicket/util/io/SerializableChecker.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/util/io/SerializableChecker.java 
b/wicket-core/src/main/java/org/apache/wicket/util/io/SerializableChecker.java
index ecb405a..8353bd9 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/util/io/SerializableChecker.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/util/io/SerializableChecker.java
@@ -16,711 +16,115 @@
  */
 package org.apache.wicket.util.io;
 
-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.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
 
-import org.apache.wicket.Component;
 import org.apache.wicket.WicketRuntimeException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.wicket.util.objects.checker.AbstractObjectChecker;
+import org.apache.wicket.util.objects.checker.ObjectChecker;
 
 
 /**
  * Utility class that analyzes objects for non-serializable nodes. Construct, 
then call
- * {@link #check(Object)} with the object you want to check. When a 
non-serializable object is
+ * {@link #writeObject(Object)} with the object you want to check. When a 
non-serializable object is
  * found, a {@link WicketNotSerializableException} is thrown with a message 
that shows the trace up
  * to the not-serializable object. The exception is thrown for the first 
non-serializable instance
  * it encounters, so multiple problems will not be shown.
- * <p>
- * As this class depends heavily on JDK's serialization internals using 
introspection, analyzing may
- * not be possible, for instance when the runtime environment does not have 
sufficient rights to set
- * fields accessible that would otherwise be hidden. You should call
- * {@link SerializableChecker#isAvailable()} to see whether this class can 
operate properly. If it
- * doesn't, you should fall back to e.g. re-throwing/ printing the {@link 
NotSerializableException}
- * you probably got before using this class.
- * </p>
- * 
+ *
  * @author eelcohillenius
  * @author Al Maw
  */
-public final class SerializableChecker extends ObjectOutputStream
+public class SerializableChecker extends ObjectChecker
 {
-
-       /** log. */
-       private static final Logger log = 
LoggerFactory.getLogger(SerializableChecker.class);
-
        /**
         * Exception that is thrown when a non-serializable object was found.
+        * @deprecated ObjectCheckException is thrown instead
         */
+       @Deprecated
        public static final class WicketNotSerializableException extends 
WicketRuntimeException
        {
                private static final long serialVersionUID = 1L;
 
-               WicketNotSerializableException(String message, Throwable cause)
+               private WicketNotSerializableException(String message, 
Throwable cause)
                {
                        super(message, cause);
                }
        }
 
        /**
-        * Does absolutely nothing.
+        * An implementation of IObjectChecker that checks whether the object
+        * implements {@link Serializable} interface
         */
-       private static class NoopOutputStream extends OutputStream
+       public static class ObjectSerializationChecker extends 
AbstractObjectChecker
        {
-               @Override
-               public void close()
-               {
-               }
+               /** Exception that should be set as the cause when throwing a 
new exception. */
+               private final NotSerializableException cause;
 
-               @Override
-               public void flush()
+               /**
+                * A constructor to use when the checker is used before a 
previous attempt to
+                * serialize the object.
+                */
+               public ObjectSerializationChecker()
                {
+                       this(null);
                }
 
-               @Override
-               public void write(byte[] b)
+               /**
+                * A constructor to use when there was a previous attempt to 
serialize the
+                * object and it failed with the {@code cause}.
+                *
+                * @param cause
+                *      the cause of the serialization failure in a previous 
attempt.
+                */
+               public ObjectSerializationChecker(NotSerializableException 
cause)
                {
+                       this.cause = cause;
                }
 
+               /**
+                * Makes the check for all objects. Exclusions by type is not 
supported.
+                * @param object
+                *      the object to check
+                * @return the {@link Result#SUCCESS} if the object can be 
serialized.
+                */
                @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 Result check(Object object)
                {
-               }
-
-               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();
-
-       /** 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 Method LOOKUP_METHOD;
-
-       private static Method GET_CLASS_DATA_LAYOUT_METHOD;
-
-       private static Method GET_NUM_OBJ_FIELDS_METHOD;
-
-       private static Method GET_OBJ_FIELD_VALUES_METHOD;
-
-       private static Method GET_FIELD_METHOD;
-
-       private static Method HAS_WRITE_REPLACE_METHOD_METHOD;
-
-       private static 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);
+                       Result result = Result.SUCCESS;
+                       if (!(object instanceof Serializable) && 
(!Proxy.isProxyClass(object.getClass())))
+                       {
+                               result = new Result(Result.Status.FAILURE, "The 
object type is not Serializable!", cause);
+                       }
 
-                       INVOKE_WRITE_REPLACE_METHOD = 
ObjectStreamClass.class.getDeclaredMethod(
-                               "invokeWriteReplace", new Class[] { 
Object.class });
-                       INVOKE_WRITE_REPLACE_METHOD.setAccessible(true);
-               }
-               catch (Exception e)
-               {
-                       log.warn("SerializableChecker not available", e);
-                       available = false;
+                       return result;
                }
        }
 
        /**
-        * 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;
-
-       /** set of classes that had no writeObject methods at lookup (to avoid 
repeated checking) */
-       private final Set<Class<?>> writeObjectMethodMissing = new 
HashSet<Class<?>>();
-
-       /** 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;
-
-       private final Stack<Object> stack = new Stack<Object>();
-
-       /**
-        * Construct.
-        * 
+        * Constructor.
+        *
         * @param exception
         *            exception that should be set as the cause when throwing a 
new exception
-        * 
+        *
         * @throws IOException
         */
        public SerializableChecker(NotSerializableException exception) throws 
IOException
        {
-               this.exception = exception;
+               super(new ObjectSerializationChecker(exception));
        }
 
        /**
-        * @see java.io.ObjectOutputStream#reset()
+        * Delegate to preserve binary compatibility.
+        *
+        * @return {@code true} if the checker can be used
+        * @deprecated Use ObjectChecker#isAvailable() instead
         */
-       @Override
-       public void reset() throws IOException
-       {
-               root = null;
-               checked.clear();
-               fieldDescription = null;
-               simpleName = null;
-               traceStack.clear();
-               nameStack.clear();
-               writeObjectMethodMissing.clear();
-       }
-
-       @Override
-       public void close() throws IOException
-       {
-               // do not call super.close() because SerializableChecker uses 
ObjectOutputStream's no-arg constructor
-
-               // just null-ify the declared members
-               reset();
-       }
-
-       private void check(Object obj)
-       {
-               if (obj == null)
-               {
-                       return;
-               }
-
-               try
-               {
-                       if (stack.contains(obj))
-                       {
-                               return;
-                       }
-               }
-               catch (RuntimeException e)
-               {
-                       log.warn("Wasn't possible to check the object " + 
obj.getClass() +
-                               " possible due an problematic implementation of 
equals method");
-                       /*
-                        * Can't check if this obj were in stack, giving up 
because we don't want to throw an
-                        * invaluable exception to user. The main goal of this 
checker is to find non
-                        * serializable data
-                        */
-                       return;
-               }
-
-               stack.push(obj);
-               try
-               {
-                       internalCheck(obj);
-               }
-               finally
-               {
-                       stack.pop();
-               }
-       }
-
-       private void internalCheck(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);
-               }
-
-               ObjectStreamClass desc;
-               for (;;)
-               {
-                       try
-                       {
-                               desc = 
(ObjectStreamClass)LOOKUP_METHOD.invoke(null, cls, Boolean.TRUE);
-                               Class<?> repCl;
-                               if 
(!(Boolean)HAS_WRITE_REPLACE_METHOD_METHOD.invoke(desc, (Object[])null) ||
-                                       (obj = 
INVOKE_WRITE_REPLACE_METHOD.invoke(desc, 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;
-                       if (writeObjectMethodMissing.contains(cls) == false)
-                       {
-                               try
-                               {
-                                       writeObjectMethod = 
cls.getDeclaredMethod("writeObject",
-                                               new Class[] { 
java.io.ObjectOutputStream.class });
-                               }
-                               catch (SecurityException e)
-                               {
-                                       // we can't access / set accessible to 
true
-                                       writeObjectMethodMissing.add(cls);
-                               }
-                               catch (NoSuchMethodException e)
-                               {
-                                       // cls doesn't have that method
-                                       writeObjectMethodMissing.add(cls);
-                               }
-                       }
-
-                       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(streamObj, 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 (Object slot : slots)
-                               {
-                                       ObjectStreamClass slotDesc;
-                                       try
-                                       {
-                                               Field descField = 
slot.getClass().getDeclaredField("desc");
-                                               descField.setAccessible(true);
-                                               slotDesc = 
(ObjectStreamClass)descField.get(slot);
-                                       }
-                                       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);
-               }
-               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, 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);
-                               }
-
-                               field.getName();
-                               simpleName = field.getName();
-                               fieldDescription = field.toString();
-                               check(objVals[i]);
-                       }
-               }
-       }
-
-       /**
-        * @return name from root to current node concatenated with slashes
-        */
-       private StringBuilder currentPath()
-       {
-               StringBuilder b = new StringBuilder();
-               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)
-       {
-               StringBuilder result = new StringBuilder();
-               StringBuilder spaces = new StringBuilder();
-               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(" <----- field that is not serializable");
-               return result.toString();
-       }
-
-       /**
-        * @see java.io.ObjectOutputStream#writeObjectOverride(java.lang.Object)
-        */
-       @Override
-       protected final void writeObjectOverride(Object obj) throws IOException
+       // TODO Wicket 7.0 - remove this method
+       @Deprecated
+       public static boolean isAvailable()
        {
-               if (!available)
-               {
-                       return;
-               }
-               root = obj;
-               if (fieldDescription == null)
-               {
-                       fieldDescription = (root instanceof Component) ? 
((Component)root).getPath() : "";
-               }
-
-               check(root);
+               return ObjectChecker.isAvailable();
        }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/8815e7c6/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/AbstractObjectChecker.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/AbstractObjectChecker.java
 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/AbstractObjectChecker.java
new file mode 100644
index 0000000..2fc427e
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/AbstractObjectChecker.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.util.objects.checker;
+
+import java.util.List;
+
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Generics;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A base class for IObjectChecker implementations which handles the logic
+ * for checking type exclusions.
+ */
+public abstract class AbstractObjectChecker implements IObjectChecker
+{
+       private static final Logger LOGGER = 
LoggerFactory.getLogger(AbstractObjectChecker.class);
+
+       private final List<Class<?>> exclusions;
+
+       protected AbstractObjectChecker()
+       {
+               this(Generics.<Class<?>>newArrayList());
+       }
+
+       protected AbstractObjectChecker(List<Class<?>> exclusions)
+       {
+               this.exclusions = Args.notNull(exclusions, "exclusions");
+       }
+
+       public Result check(Object object)
+       {
+               Result result = Result.SUCCESS;
+
+               if (object != null && getExclusions().isEmpty() == false)
+               {
+                       Class<?> objectType = object.getClass();
+                       for (Class<?> excludedType : getExclusions())
+                       {
+                               if (excludedType.isAssignableFrom(objectType))
+                               {
+                                       LOGGER.debug("Object with type '{}' 
wont be checked because its type is excluded ({})",
+                                                       objectType, 
excludedType);
+                                       return result;
+                               }
+                       }
+               }
+
+               result = doCheck(object);
+
+               return result;
+       }
+
+       /**
+        * The implementations should make the specific check on the object.
+        * @param object
+        *      the object to check
+        * @return the {@link Result result} of the specific check
+        */
+       protected Result doCheck(Object object)
+       {
+               return Result.SUCCESS;
+       }
+
+       public List<Class<?>> getExclusions()
+       {
+               return exclusions;
+       }
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/8815e7c6/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/IObjectChecker.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/IObjectChecker.java
 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/IObjectChecker.java
new file mode 100644
index 0000000..a7b72bc
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/IObjectChecker.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.util.objects.checker;
+
+import java.util.List;
+
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * IObjectChecker can be used to check whether an object has/has not given 
state
+ * before serializing it. The serialization will be stopped if the object 
doesn't pass
+ * the {@code #check(Object) check}.
+ */
+public interface IObjectChecker
+{
+       /**
+        * Represents the result of a check.
+        */
+       public static class Result
+       {
+               public static enum Status
+               {
+                       /**
+                        * The check is successful
+                        */
+                       SUCCESS,
+
+                       /**
+                        * The check failed for some reason
+                        */
+                       FAILURE
+               }
+
+               /**
+                * A singleton that can be used for successful checks
+                */
+               public static final Result SUCCESS = new Result(Status.SUCCESS, 
"");
+
+               /**
+                * The status of the check.
+                */
+               public final Status status;
+
+               /**
+                * The reason why a check succeeded/failed. Mandatory in 
failure case.
+                */
+               public final String reason;
+
+               /**
+                * An optional cause of a failure.
+                */
+               public final Throwable cause;
+
+               /**
+                * Constructor.
+                *
+                * @param status
+                *      the status of the result
+                * @param reason
+                *      the reason of successful/failed check
+                */
+               public Result(Status status, String reason)
+               {
+                       this(status, reason, null);
+               }
+
+
+               /**
+                * Constructor.
+                *
+                * @param status
+                *      the status of the result
+                * @param reason
+                *      the reason of successful/failed check
+                * @param cause
+                *      the cause of a failure. Optional.
+                */
+               public Result(Status status, String reason, Throwable cause)
+               {
+                       if (status == Status.FAILURE)
+                       {
+                               Args.notEmpty(reason, "reason");
+                       }
+                       this.status = status;
+                       this.reason = reason;
+                       this.cause = cause;
+               }
+
+               @Override
+               public String toString()
+               {
+                       return "Result{" +
+                                       "reason='" + reason + '\'' +
+                                       ", status=" + status +
+                                       '}';
+               }
+       }
+
+       /**
+        * Checks an object that it meets some requirements before serializing 
it
+        *
+        * @param object
+        *      the object to check
+        * @return a Result object describing whether the check is successful 
or not
+        */
+       Result check(Object object);
+
+       /**
+        * @return A list of types which should not be checked by this checker
+        */
+       List<Class<?>> getExclusions();
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/8815e7c6/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/NotDetachedModelChecker.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/NotDetachedModelChecker.java
 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/NotDetachedModelChecker.java
new file mode 100644
index 0000000..48c3378
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/NotDetachedModelChecker.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.util.objects.checker;
+
+import java.util.List;
+
+import org.apache.wicket.model.LoadableDetachableModel;
+
+/**
+ * An implementation of {@link IObjectChecker} that returns a failure
+ * result when the checked object is a {@link 
org.apache.wicket.model.LoadableDetachableModel}
+ * and it is model object is still attached.
+ */
+public class NotDetachedModelChecker extends AbstractObjectChecker
+{
+       /**
+        * Constructor.
+        *
+        * Checks all passed objects.
+        */
+       public NotDetachedModelChecker()
+       {
+               super();
+       }
+
+       /**
+        * Constructor.
+        *
+        * Checks objects which types are not excluded.
+        *
+        * @param exclusions
+        *      a list of types which should not be checked
+        */
+       public NotDetachedModelChecker(List<Class<?>> exclusions)
+       {
+               super(exclusions);
+       }
+
+       @Override
+       public Result doCheck(Object obj)
+       {
+               Result result = Result.SUCCESS;
+
+               if (obj instanceof LoadableDetachableModel<?>)
+               {
+                       LoadableDetachableModel<?> model = 
(LoadableDetachableModel<?>) obj;
+                       if (model.isAttached())
+                       {
+                               result = new Result(Result.Status.FAILURE, "Not 
detached model found!");
+                       }
+               }
+
+               return result;
+       }
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/8815e7c6/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/ObjectChecker.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/ObjectChecker.java
 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/ObjectChecker.java
new file mode 100644
index 0000000..3109981
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/ObjectChecker.java
@@ -0,0 +1,709 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.util.objects.checker;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.ObjectStreamField;
+import java.io.OutputStream;
+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.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.util.lang.Classes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Checks an object tree during serialization for wrong state by delegating 
the work
+ * to the used {@link IObjectChecker IObjectChecker}s.
+ * <p>
+ * As this class depends heavily on JDK's serialization internals using 
introspection, analyzing may
+ * not be possible, for instance when the runtime environment does not have 
sufficient rights to set
+ * fields accessible that would otherwise be hidden. You should call
+ * {@link ObjectChecker#isAvailable()} to see whether this class can operate 
properly.
+ * </p>
+ */
+public class ObjectChecker extends ObjectOutputStream
+{
+       private static final Logger log = 
LoggerFactory.getLogger(ObjectChecker.class);
+
+       public static class ObjectCheckException extends WicketRuntimeException
+       {
+               public ObjectCheckException(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)
+               {
+                       this.object = object;
+                       this.fieldDescription = fieldDescription;
+               }
+
+               @Override
+               public String toString()
+               {
+                       return object.getClass() + " - " + fieldDescription;
+               }
+       }
+
+       private static final NoopOutputStream DUMMY_OUTPUT_STREAM = new 
NoopOutputStream();
+
+       /** 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 Method LOOKUP_METHOD;
+
+       private static Method GET_CLASS_DATA_LAYOUT_METHOD;
+
+       private static Method GET_NUM_OBJ_FIELDS_METHOD;
+
+       private static Method GET_OBJ_FIELD_VALUES_METHOD;
+
+       private static Method GET_FIELD_METHOD;
+
+       private static Method HAS_WRITE_REPLACE_METHOD_METHOD;
+
+       private static 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 (Exception e)
+               {
+                       log.warn("SerializableChecker not available", e);
+                       available = false;
+               }
+       }
+
+       private final IObjectChecker[] checkers;
+
+       /**
+        * 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 
java.io.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 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<CharSequence> nameStack = new 
LinkedList<CharSequence>();
+
+       /** root object being analyzed. */
+       private Object root;
+
+       /** set of classes that had no writeObject methods at lookup (to avoid 
repeated checking) */
+       private final Set<Class<?>> writeObjectMethodMissing = new 
HashSet<Class<?>>();
+
+       /** current simple field name. */
+       private CharSequence simpleName = "";
+
+       /** current full field description. */
+       private String fieldDescription;
+
+       private final Stack<Object> stack = new Stack<Object>();
+
+       /**
+        * Constructor.
+        *
+        * @param checkers
+        *      the {@link IObjectChecker checkers} that will actually check 
the objects
+        * @throws java.io.IOException
+        * @throws SecurityException
+        */
+       public ObjectChecker(final IObjectChecker... checkers) throws 
IOException, SecurityException
+       {
+               this.checkers = checkers;
+       }
+
+       private void check(Object obj)
+       {
+               if (obj == null)
+               {
+                       return;
+               }
+
+               try
+               {
+                       if (stack.contains(obj))
+                       {
+                               return;
+                       }
+               }
+               catch (RuntimeException e)
+               {
+                       log.warn("Wasn't possible to check the object '{}' 
possible due an problematic " +
+                                       "implementation of equals method", 
obj.getClass());
+                       /*
+                        * Can't check if this obj were in stack, giving up 
because we don't want to throw an
+                        * invaluable exception to user. The main goal of this 
checker is to find non
+                        * serializable data
+                        */
+                       return;
+               }
+
+               stack.push(obj);
+               try
+               {
+                       internalCheck(obj);
+               }
+               finally
+               {
+                       stack.pop();
+               }
+       }
+
+       private void internalCheck(Object obj)
+       {
+               if (obj == null)
+               {
+                       return;
+               }
+
+               Class<?> cls = obj.getClass();
+               nameStack.add(simpleName);
+               traceStack.add(new TraceSlot(obj, fieldDescription));
+
+               for (IObjectChecker checker : checkers)
+               {
+                       IObjectChecker.Result result = checker.check(obj);
+                       if (result.status == 
IObjectChecker.Result.Status.FAILURE)
+                       {
+                               String prettyPrintMessage = 
toPrettyPrintedStack(Classes.name(cls));
+                               String exceptionMessage = result.reason + '\n' 
+ prettyPrintMessage;
+                               throw new 
ObjectCheckException(exceptionMessage, result.cause);
+                       }
+               }
+
+               ObjectStreamClass desc;
+               for (;;)
+               {
+                       try
+                       {
+                               desc = 
(ObjectStreamClass)LOOKUP_METHOD.invoke(null, cls, Boolean.TRUE);
+                               Class<?> repCl;
+                               if 
(!(Boolean)HAS_WRITE_REPLACE_METHOD_METHOD.invoke(desc, (Object[])null) ||
+                                               (obj = 
INVOKE_WRITE_REPLACE_METHOD.invoke(desc, 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++)
+                               {
+                                       CharSequence arrayPos = new 
StringBuilder(4).append('[').append(i).append(']');
+                                       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);
+                                               CharSequence arrayPos = new 
StringBuilder(10).append("[write:").append(count++).append(']');
+                                               simpleName = arrayPos;
+                                               fieldDescription += arrayPos;
+
+                                               check(streamObj);
+                                       }
+                               });
+                       }
+                       catch (Exception e)
+                       {
+                               if (e instanceof ObjectCheckException)
+                               {
+                                       throw (ObjectCheckException)e;
+                               }
+                               log.warn("Error delegating to Externalizable : 
{}, path: {}", e.getMessage(), currentPath());
+                       }
+               }
+               else
+               {
+                       Method writeObjectMethod = null;
+                       if (writeObjectMethodMissing.contains(cls) == false)
+                       {
+                               try
+                               {
+                                       writeObjectMethod = 
cls.getDeclaredMethod("writeObject",
+                                                       new Class[] { 
java.io.ObjectOutputStream.class });
+                               }
+                               catch (SecurityException e)
+                               {
+                                       // we can't access / set accessible to 
true
+                                       writeObjectMethodMissing.add(cls);
+                               }
+                               catch (NoSuchMethodException e)
+                               {
+                                       // cls doesn't have that method
+                                       writeObjectMethodMissing.add(cls);
+                               }
+                       }
+
+                       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(streamObj, null);
+                                               CharSequence arrayPos = new 
StringBuilder(10).append("[write:").append(counter).append(']');
+                                               simpleName = arrayPos;
+                                               fieldDescription += arrayPos;
+                                               check(streamObj);
+                                               return streamObj;
+                                       }
+                               }
+                               try
+                               {
+                                       InterceptingObjectOutputStream ioos = 
new InterceptingObjectOutputStream();
+                                       ioos.writeObject(obj);
+                               }
+                               catch (Exception e)
+                               {
+                                       if (e instanceof ObjectCheckException)
+                                       {
+                                               throw (ObjectCheckException)e;
+                                       }
+                                       log.warn("error delegating to 
writeObject : {}, path: {}", e.getMessage(), currentPath());
+                               }
+                       }
+                       else
+                       {
+                               Object[] slots;
+                               try
+                               {
+                                       slots = 
(Object[])GET_CLASS_DATA_LAYOUT_METHOD.invoke(desc, (Object[])null);
+                               }
+                               catch (Exception e)
+                               {
+                                       throw new RuntimeException(e);
+                               }
+                               for (Object slot : slots)
+                               {
+                                       ObjectStreamClass slotDesc;
+                                       try
+                                       {
+                                               Field descField = 
slot.getClass().getDeclaredField("desc");
+                                               descField.setAccessible(true);
+                                               slotDesc = 
(ObjectStreamClass)descField.get(slot);
+                                       }
+                                       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);
+               }
+               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, 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);
+                               }
+
+                               simpleName = field.getName();
+                               fieldDescription = field.toString();
+                               check(objVals[i]);
+                       }
+               }
+       }
+
+       /**
+        * @return name from root to current node concatenated with slashes
+        */
+       private StringBuilder currentPath()
+       {
+               StringBuilder b = new StringBuilder();
+               for (Iterator<CharSequence> 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
+        */
+       protected final String toPrettyPrintedStack(String type)
+       {
+               StringBuilder result = new StringBuilder(512);
+               StringBuilder spaces = new StringBuilder(32);
+               result.append("A problem occurred while checking object with 
type: ");
+               result.append(type);
+               result.append("\nField hierarchy is:");
+               for (TraceSlot slot : traceStack)
+               {
+                       spaces.append(' ').append(' ');
+                       
result.append('\n').append(spaces).append(slot.fieldDescription);
+                       result.append(" 
[class=").append(Classes.name(slot.object.getClass()));
+                       if (slot.object instanceof Component)
+                       {
+                               Component component = (Component)slot.object;
+                               result.append(", 
path=").append(component.getPath());
+                       }
+                       result.append(']');
+               }
+               result.append(" <----- field that is causing the problem");
+               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);
+       }
+
+       /**
+        * @see java.io.ObjectOutputStream#reset()
+        */
+       @Override
+       public void reset() throws IOException
+       {
+               root = null;
+               checked.clear();
+               fieldDescription = null;
+               simpleName = null;
+               traceStack.clear();
+               nameStack.clear();
+               writeObjectMethodMissing.clear();
+       }
+
+       @Override
+       public void close() throws IOException
+       {
+               // do not call super.close() because SerializableChecker uses 
ObjectOutputStream's no-arg constructor
+
+               // just null-ify the declared members
+               reset();
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/8815e7c6/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/OrphanComponentChecker.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/OrphanComponentChecker.java
 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/OrphanComponentChecker.java
new file mode 100644
index 0000000..b3f5678
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/util/objects/checker/OrphanComponentChecker.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.util.objects.checker;
+
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+
+/**
+ * A checker that doesn't allow the serialization of {@link 
org.apache.wicket.Component component}s
+ * which are not a {@link org.apache.wicket.Page page} and have no parent 
component.
+ *
+ * <p>
+ *     Note: The Wizard component from wicket-extensions use such kind of 
orphaned components
+ *     and will fail this check unless the step classes are specified as 
exclusions.
+ * </p>
+ */
+public class OrphanComponentChecker extends AbstractObjectChecker
+{
+       /**
+        * Constructor.
+        *
+        * Checks all passed objects.
+        */
+       public OrphanComponentChecker()
+       {
+               super();
+       }
+
+       /**
+        * Constructor.
+        *
+        * Checks objects which types are not excluded.
+        *
+        * @param exclusions
+        *      a list of types which should not be checked
+        */
+       public OrphanComponentChecker(List<Class<?>> exclusions)
+       {
+               super(exclusions);
+       }
+
+       @Override
+       public Result doCheck(Object object)
+       {
+               Result result = Result.SUCCESS;
+
+               if (object instanceof Component)
+               {
+                       Component component = (Component) object;
+                       if (component instanceof Page == false && 
component.getParent() == null)
+                       {
+                               result = new Result(Result.Status.FAILURE, "A 
component without a parent is detected.");
+                       }
+               }
+
+               return result;
+       }
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/8815e7c6/wicket-core/src/test/java/org/apache/wicket/util/io/SerializableCheckerTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/util/io/SerializableCheckerTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/util/io/SerializableCheckerTest.java
index 84a50a2..3331c8d 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/util/io/SerializableCheckerTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/util/io/SerializableCheckerTest.java
@@ -24,7 +24,7 @@ import org.apache.log4j.Level;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.wicket.util.Log4jEventHistory;
-import 
org.apache.wicket.util.io.SerializableChecker.WicketNotSerializableException;
+import org.apache.wicket.util.objects.checker.ObjectChecker;
 import org.apache.wicket.util.value.ValueMap;
 import org.junit.Assert;
 import org.junit.Test;
@@ -57,7 +57,7 @@ public class SerializableCheckerTest extends Assert
        @Test
        public void runtimeExceptionTolerance() throws IOException
        {
-               Logger logger = LogManager.getLogger(SerializableChecker.class);
+               Logger logger = LogManager.getLogger(ObjectChecker.class);
                logger.setLevel(Level.WARN);
                Log4jEventHistory logHistory = new Log4jEventHistory();
                logger.addAppender(logHistory);
@@ -66,7 +66,7 @@ public class SerializableCheckerTest extends Assert
                try
                {
                        serializableChecker.writeObject(new TestType1());
-                       String expectedMessage = "Wasn't possible to check the 
object class org.apache.wicket.util.io.SerializableCheckerTest$ProblematicType 
possible due an problematic implementation of equals method";
+                       String expectedMessage = "Wasn't possible to check the 
object 'class 
org.apache.wicket.util.io.SerializableCheckerTest$ProblematicType' possible due 
an problematic implementation of equals method";
                        assertTrue(logHistory.contains(Level.WARN, 
expectedMessage));
                }
                catch (TestException notMeaningfulException)
@@ -88,7 +88,7 @@ public class SerializableCheckerTest extends Assert
                {
                        serializableChecker.writeObject(new TestType2());
                }
-               catch (WicketNotSerializableException e)
+               catch (ObjectChecker.ObjectCheckException e)
                {
                        exceptionMessage = e.getMessage();
                }

Reply via email to