You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2012/10/15 11:34:32 UTC

[6/7] git commit: WICKET-4812 Make SerializationChecker easier for extending so custom checks can be added to it

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/6014d8bb
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/6014d8bb
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/6014d8bb

Branch: refs/heads/master
Commit: 6014d8bb92595bf486d69f7a5c1c798bcdc252f1
Parents: 40512f8
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
Authored: Wed Oct 10 15:47:31 2012 +0300
Committer: Martin Tzvetanov Grigorov <mg...@apache.org>
Committed: Wed Oct 10 15:47:31 2012 +0300

----------------------------------------------------------------------
 .../wicket/core/util/io/SerializableChecker.java   |  707 +-------------
 .../core/util/objects/checker/IObjectChecker.java  |   94 ++
 .../objects/checker/NotDetachedModelChecker.java   |   28 +
 .../core/util/objects/checker/ObjectChecker.java   |  739 +++++++++++++++
 .../wicket/serialize/java/JavaSerializer.java      |   64 ++-
 .../wicket/serialize/java/JavaSerializerTest.java  |   85 ++
 6 files changed, 1050 insertions(+), 667 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/6014d8bb/wicket-core/src/main/java/org/apache/wicket/core/util/io/SerializableChecker.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/io/SerializableChecker.java b/wicket-core/src/main/java/org/apache/wicket/core/util/io/SerializableChecker.java
index 57f8317..fb15cbc 100644
--- a/wicket-core/src/main/java/org/apache/wicket/core/util/io/SerializableChecker.java
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/io/SerializableChecker.java
@@ -16,62 +16,34 @@
  */
 package org.apache.wicket.core.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.core.util.objects.checker.IObjectChecker;
+import org.apache.wicket.core.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 Use ObjectChecker.WicketNotSerializableException instead
 	 */
-	public static final class WicketNotSerializableException extends WicketRuntimeException
+	// TODO Wicket 7.0 - remove this class. It is here only for backward binary compatibility
+	@Deprecated
+	public static final class WicketNotSerializableException extends ObjectChecker.WicketNotSerializableException
 	{
 		private static final long serialVersionUID = 1L;
 
@@ -82,247 +54,50 @@ public final class SerializableChecker extends ObjectOutputStream
 	}
 
 	/**
-	 * Does absolutely nothing.
+	 * An implementation of IObjectChecker that checks whether the object
+	 * implements {@link Serializable} interface
 	 */
-	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 static class ObjectSerializationChecker implements IObjectChecker
 	{
+		/** Exception that should be set as the cause when throwing a new exception. */
+		private final NotSerializableException cause;
 
-		@Override
-		public void close() throws IOException
-		{
-		}
-
-		@Override
-		public void flush() throws IOException
+		/**
+		 * 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) throws IOException
+		/**
+		 * 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;
 		}
 
 		@Override
-		public void write(byte[] b, int off, int len) throws IOException
+		public Result check(Object object)
 		{
-		}
-
-		@Override
-		public void write(int b) throws IOException
-		{
-		}
-
-		@Override
-		public void writeBoolean(boolean v) throws IOException
-		{
-		}
-
-		@Override
-		public void writeByte(int v) throws IOException
-		{
-		}
-
-		@Override
-		public void writeBytes(String s) throws IOException
-		{
-		}
-
-		@Override
-		public void writeChar(int v) throws IOException
-		{
-		}
-
-		@Override
-		public void writeChars(String s) throws IOException
-		{
-		}
-
-		@Override
-		public void writeDouble(double v) throws IOException
-		{
-		}
-
-		@Override
-		public void writeFloat(float v) throws IOException
-		{
-		}
-
-		@Override
-		public void writeInt(int v) throws IOException
-		{
-		}
-
-		@Override
-		public void writeLong(long v) throws IOException
-		{
-		}
-
-		@Override
-		public void writeShort(int v) throws IOException
-		{
-		}
-
-		@Override
-		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
@@ -331,413 +106,19 @@ public final class SerializableChecker extends ObjectOutputStream
 	 */
 	public SerializableChecker(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();
-		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;
-
-					@Override
-					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;
+		super(new ObjectSerializationChecker(exception));
 	}
 
 	/**
-	 * Dump with indentation.
+	 * Delegate to preserve binary compatibility.
 	 *
-	 * @param type
-	 *            the type that couldn't be serialized
-	 * @return A very pretty dump
+	 * @return {@code true} if the checker can be used
+	 * @deprecated Use ObjectChecker#isAvailable() instead
 	 */
-	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/6014d8bb/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/IObjectChecker.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/IObjectChecker.java b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/IObjectChecker.java
new file mode 100644
index 0000000..85ff014
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/IObjectChecker.java
@@ -0,0 +1,94 @@
+package org.apache.wicket.core.util.objects.checker;
+
+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;
+		}
+	}
+
+	/**
+	 * 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);
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/6014d8bb/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/NotDetachedModelChecker.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/NotDetachedModelChecker.java b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/NotDetachedModelChecker.java
new file mode 100644
index 0000000..969a00a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/NotDetachedModelChecker.java
@@ -0,0 +1,28 @@
+package org.apache.wicket.core.util.objects.checker;
+
+import org.apache.wicket.model.LoadableDetachableModel;
+
+/**
+ * An implementation of {@link IObjectChecker} that returns a failure
+ * result when the checked object is a {@link LoadableDetachableModel}
+ * and it is model object is still attached.
+ */
+public class NotDetachedModelChecker implements IObjectChecker
+{
+	@Override
+	public Result check(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/6014d8bb/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/ObjectChecker.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/ObjectChecker.java b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/ObjectChecker.java
new file mode 100644
index 0000000..629e580
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/ObjectChecker.java
@@ -0,0 +1,739 @@
+package org.apache.wicket.core.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)
+		{
+			super(message);
+		}
+
+		public ObjectCheckException(String message, Throwable cause)
+		{
+			super(message, cause);
+		}
+	}
+
+	/**
+	 * Exception that is thrown when a non-serializable object was found.
+	 */
+	public static class WicketNotSerializableException extends WicketRuntimeException
+	{
+		private static final long serialVersionUID = 1L;
+
+		protected 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
+	{
+
+		@Override
+		public void close() throws IOException
+		{
+		}
+
+		@Override
+		public void flush() throws IOException
+		{
+		}
+
+		@Override
+		public void write(byte[] b) throws IOException
+		{
+		}
+
+		@Override
+		public void write(byte[] b, int off, int len) throws IOException
+		{
+		}
+
+		@Override
+		public void write(int b) throws IOException
+		{
+		}
+
+		@Override
+		public void writeBoolean(boolean v) throws IOException
+		{
+		}
+
+		@Override
+		public void writeByte(int v) throws IOException
+		{
+		}
+
+		@Override
+		public void writeBytes(String s) throws IOException
+		{
+		}
+
+		@Override
+		public void writeChar(int v) throws IOException
+		{
+		}
+
+		@Override
+		public void writeChars(String s) throws IOException
+		{
+		}
+
+		@Override
+		public void writeDouble(double v) throws IOException
+		{
+		}
+
+		@Override
+		public void writeFloat(float v) throws IOException
+		{
+		}
+
+		@Override
+		public void writeInt(int v) throws IOException
+		{
+		}
+
+		@Override
+		public void writeLong(long v) throws IOException
+		{
+		}
+
+		@Override
+		public void writeShort(int v) throws IOException
+		{
+		}
+
+		@Override
+		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);
+
+			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 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;
+
+	private final Stack<Object> stack = new Stack<Object>();
+
+	/**
+	 * Constructor.
+	 *
+	 * @throws 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 " + 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));
+
+		for (IObjectChecker checker : checkers)
+		{
+			IObjectChecker.Result result = checker.check(obj);
+			if (result.status == IObjectChecker.Result.Status.FAILURE)
+			{
+				ObjectCheckException ocx;
+				String prettyPrintMessage = toPrettyPrintedStack(Classes.name(cls));
+				String exceptionMessage = result.reason + '\n' + prettyPrintMessage;
+				if (result.cause != null)
+				{
+					ocx = new ObjectCheckException(exceptionMessage, result.cause);
+				}
+				else
+				{
+					ocx = new ObjectCheckException(exceptionMessage);
+				}
+				throw ocx;
+			}
+		}
+
+		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;
+
+					@Override
+					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
+	 */
+	protected final String toPrettyPrintedStack(String type)
+	{
+		StringBuilder result = new StringBuilder();
+		StringBuilder spaces = new StringBuilder();
+		result.append("A problem occurred while checking object with type: ");
+		result.append(type);
+		result.append("\nField hierarchy is:");
+		for (TraceSlot slot : traceStack)
+		{
+			spaces.append("  ");
+			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 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/6014d8bb/wicket-core/src/main/java/org/apache/wicket/serialize/java/JavaSerializer.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/serialize/java/JavaSerializer.java b/wicket-core/src/main/java/org/apache/wicket/serialize/java/JavaSerializer.java
index 57ff0c7..bb23879 100644
--- a/wicket-core/src/main/java/org/apache/wicket/serialize/java/JavaSerializer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/serialize/java/JavaSerializer.java
@@ -31,6 +31,8 @@ import org.apache.wicket.ThreadContext;
 import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.application.IClassResolver;
 import org.apache.wicket.core.util.io.SerializableChecker;
+import org.apache.wicket.core.util.objects.checker.IObjectChecker;
+import org.apache.wicket.core.util.objects.checker.ObjectChecker;
 import org.apache.wicket.serialize.ISerializer;
 import org.apache.wicket.settings.IApplicationSettings;
 import org.apache.wicket.util.io.IOUtils;
@@ -172,7 +174,7 @@ public class JavaSerializer implements ISerializer
 	 */
 	protected ObjectOutputStream newObjectOutputStream(OutputStream out) throws IOException
 	{
-		return new CheckerObjectOutputStream(out);
+		return new SerializationCheckerObjectOutputStream(out);
 	}
 
 	/**
@@ -234,11 +236,11 @@ public class JavaSerializer implements ISerializer
 	 * Write objects to the wrapped output stream and log a meaningful message for serialization
 	 * problems
 	 */
-	private static class CheckerObjectOutputStream extends ObjectOutputStream
+	private static class SerializationCheckerObjectOutputStream extends ObjectOutputStream
 	{
 		private final ObjectOutputStream oos;
 
-		public CheckerObjectOutputStream(OutputStream out) throws IOException
+		public SerializationCheckerObjectOutputStream(OutputStream out) throws IOException
 		{
 			oos = new ObjectOutputStream(out);
 		}
@@ -252,7 +254,7 @@ public class JavaSerializer implements ISerializer
 			}
 			catch (NotSerializableException nsx)
 			{
-				if (SerializableChecker.isAvailable())
+				if (ObjectChecker.isAvailable())
 				{
 					// trigger serialization again, but this time gather
 					// some more info
@@ -282,4 +284,58 @@ public class JavaSerializer implements ISerializer
 			oos.close();
 		}
 	}
+
+	/**
+	 * An ObjectOutputStream that uses {@link IObjectChecker IObjectChecker}s to check the
+	 * state of the object before serializing it. If the checker returns
+	 * {@link org.apache.wicket.core.util.objects.checker.IObjectChecker.Result.Status#FAILURE}
+	 * then the serialization process is stopped and the error is logged.
+	 */
+	public static class ObjectCheckerObjectOutputStream extends ObjectOutputStream
+	{
+		private final ObjectOutputStream oos;
+
+		/**
+		 * The {@link IObjectChecker checkers} to use during the serialization
+		 */
+		private final IObjectChecker[] checkers;
+
+		public ObjectCheckerObjectOutputStream(OutputStream out, IObjectChecker... checkers) throws IOException
+		{
+			oos = new ObjectOutputStream(out);
+			this.checkers = checkers;
+		}
+
+		@Override
+		protected final void writeObjectOverride(Object obj) throws IOException
+		{
+			try
+			{
+				if (ObjectChecker.isAvailable())
+				{
+					ObjectChecker checker = new ObjectChecker(checkers);
+					checker.writeObject(obj);
+				}
+
+				oos.writeObject(obj);
+			}
+			catch (Exception e)
+			{
+				log.error("error writing object " + obj + ": " + e.getMessage(), e);
+				throw new WicketRuntimeException(e);
+			}
+		}
+
+		@Override
+		public void flush() throws IOException
+		{
+			oos.flush();
+		}
+
+		@Override
+		public void close() throws IOException
+		{
+			oos.close();
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/6014d8bb/wicket-core/src/test/java/org/apache/wicket/serialize/java/JavaSerializerTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/serialize/java/JavaSerializerTest.java b/wicket-core/src/test/java/org/apache/wicket/serialize/java/JavaSerializerTest.java
new file mode 100644
index 0000000..31086b3
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/serialize/java/JavaSerializerTest.java
@@ -0,0 +1,85 @@
+package org.apache.wicket.serialize.java;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+import org.apache.wicket.WicketTestCase;
+import org.apache.wicket.core.util.objects.checker.IObjectChecker;
+import org.apache.wicket.core.util.objects.checker.NotDetachedModelChecker;
+import org.apache.wicket.markup.html.WebComponent;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class JavaSerializerTest extends WicketTestCase
+{
+	/**
+	 * https://issues.apache.org/jira/browse/WICKET-4812
+	 *
+	 * Tests that the serialization fails when a checking ObjectOutputStream is
+	 * used with NotDetachedModelChecker and there is a non-detached LoadableDetachableModel
+	 * in the object tree.
+	 */
+	@Test
+	public void notDetachedModel()
+	{
+		JavaSerializer serializer = new JavaSerializer("JavaSerializerTest")
+		{
+			@Override
+			protected ObjectOutputStream newObjectOutputStream(OutputStream out) throws IOException
+			{
+				IObjectChecker checker = new NotDetachedModelChecker();
+				return new ObjectCheckerObjectOutputStream(out, checker);
+			}
+		};
+
+		IModel<String> model = new NotDetachedModel();
+		model.getObject();
+		WebComponent component = new WebComponent("id", model);
+		byte[] serialized = serializer.serialize(component);
+		assertNull("The produced byte[] must be null if there was an error", serialized);
+	}
+
+	/**
+	 * A Model used for #notDetachedModel() test
+	 */
+	private static class NotDetachedModel extends LoadableDetachableModel<String>
+	{
+		@Override
+		protected String load()
+		{
+			return "loaded";
+		}
+	}
+
+	/**
+	 * https://issues.apache.org/jira/browse/WICKET-4812
+	 * 
+	 * Tests that serialization fails when using the default ObjectOutputStream in
+	 * JavaSerializer and some object in the tree is not Serializable
+	 */
+	@Test
+	public void notSerializable()
+	{
+		JavaSerializer serializer = new JavaSerializer("JavaSerializerTest");
+		WebComponent component = new NotSerializableComponent("id");
+		byte[] serialized = serializer.serialize(component);
+		assertNull("The produced byte[] must be null if there was an error", serialized);
+	}
+
+	private static class NotSerializableComponent extends WebComponent
+	{
+		private final NotSerializableObject member = new NotSerializableObject();
+
+		public NotSerializableComponent(final String id)
+		{
+			super(id);
+		}
+	}
+
+	private static class NotSerializableObject {}
+}