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/17 10:36:57 UTC

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

Updated Branches:
  refs/heads/master bbd582f9b -> 48350b07f


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

Rename ObjectChecker to CheckingObjectOutputStream to make it more clear that it is not an implementation of IObjectChecker.
Simplify JavaSerializer to use CheckingObjectOutputStream directly without another ObjectOutputStream as adapter.


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

Branch: refs/heads/master
Commit: 48350b07fae26546ddf58e7fa8f4981799cab458
Parents: 13c6270
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
Authored: Wed Oct 17 10:33:24 2012 +0200
Committer: Martin Tzvetanov Grigorov <mg...@apache.org>
Committed: Wed Oct 17 10:33:24 2012 +0200

----------------------------------------------------------------------
 .../wicket/core/util/io/SerializableChecker.java   |   27 +-
 .../checker/CheckingObjectOutputStream.java        |  741 +++++++++++++++
 .../core/util/objects/checker/ObjectChecker.java   |  726 --------------
 .../wicket/serialize/java/JavaSerializer.java      |   68 +--
 .../wicket/serialize/java/JavaSerializerTest.java  |    7 +-
 .../wicket/util/io/SerializableCheckerTest.java    |   14 +-
 6 files changed, 782 insertions(+), 801 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/48350b07/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 7b43caf..76c0295 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
@@ -18,25 +18,27 @@ package org.apache.wicket.core.util.io;
 
 import java.io.IOException;
 import java.io.NotSerializableException;
+import java.io.OutputStream;
 import java.io.Serializable;
 import java.lang.reflect.Proxy;
 
 import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.core.util.objects.checker.AbstractObjectChecker;
-import org.apache.wicket.core.util.objects.checker.ObjectChecker;
+import org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream;
+import org.apache.wicket.util.io.ByteArrayOutputStream;
 
 
 /**
  * Utility class that analyzes objects for non-serializable nodes. Construct, then call
  * {@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
+ * found, a {@link ObjectCheckException} 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.
  *
  * @author eelcohillenius
  * @author Al Maw
  */
-public class SerializableChecker extends ObjectChecker
+public class SerializableChecker extends CheckingObjectOutputStream
 {
 	/**
 	 * Exception that is thrown when a non-serializable object was found.
@@ -112,7 +114,22 @@ public class SerializableChecker extends ObjectChecker
 	 */
 	public SerializableChecker(NotSerializableException exception) throws IOException
 	{
-		super(new ObjectSerializationChecker(exception));
+		this(new ByteArrayOutputStream(), exception);
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param exception
+	 *      exception that should be set as the cause when throwing a new exception
+	 * @param outputStream
+	 *      the output stream where the serialized object will be written upon successful check
+	 *
+	 * @throws IOException
+	 */
+	public SerializableChecker(final OutputStream outputStream, NotSerializableException exception) throws IOException
+	{
+		super(outputStream, new ObjectSerializationChecker(exception));
 	}
 
 	/**
@@ -125,6 +142,6 @@ public class SerializableChecker extends ObjectChecker
 	@Deprecated
 	public static boolean isAvailable()
 	{
-		return ObjectChecker.isAvailable();
+		return CheckingObjectOutputStream.isAvailable();
 	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/48350b07/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/CheckingObjectOutputStream.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/CheckingObjectOutputStream.java b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/CheckingObjectOutputStream.java
new file mode 100644
index 0000000..d6e5715
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/CheckingObjectOutputStream.java
@@ -0,0 +1,741 @@
+/*
+ * 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.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 CheckingObjectOutputStream#isAvailable()} to see whether this class can operate properly.
+ *
+ *
+ * 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.
+ * </p>
+ */
+public class CheckingObjectOutputStream extends ObjectOutputStream
+{
+	private static final Logger log = LoggerFactory.getLogger(CheckingObjectOutputStream.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
+	{
+
+		@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)
+		{
+			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;
+	}
+
+	/**
+	 * The output stream where the serialized object will be written upon successful check
+	 */
+	private final ObjectOutputStream out;
+
+	/** 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 outputStream
+	 *      the output stream where the serialized object will be written upon successful check
+	 * @param checkers
+	 *      the {@link IObjectChecker checkers} that will actually check the objects
+	 * @throws IOException
+	 * @throws SecurityException
+	 */
+	public CheckingObjectOutputStream(final OutputStream outputStream, final IObjectChecker... checkers) throws IOException, SecurityException
+	{
+		this.out = new ObjectOutputStream(outputStream);
+		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;
+
+					@Override
+					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);
+		out.writeObject(obj);
+	}
+
+	/**
+	 * @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/48350b07/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
deleted file mode 100644
index ab328d1..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/ObjectChecker.java
+++ /dev/null
@@ -1,726 +0,0 @@
-/*
- * 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.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, 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)
-		{
-			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 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;
-
-					@Override
-					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/48350b07/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 bb23879..44dae8b 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,8 +31,7 @@ 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.core.util.objects.checker.CheckingObjectOutputStream;
 import org.apache.wicket.serialize.ISerializer;
 import org.apache.wicket.settings.IApplicationSettings;
 import org.apache.wicket.util.io.IOUtils;
@@ -238,11 +237,14 @@ public class JavaSerializer implements ISerializer
 	 */
 	private static class SerializationCheckerObjectOutputStream extends ObjectOutputStream
 	{
+		private final OutputStream outputStream;
+
 		private final ObjectOutputStream oos;
 
-		public SerializationCheckerObjectOutputStream(OutputStream out) throws IOException
+		private SerializationCheckerObjectOutputStream(OutputStream outputStream) throws IOException
 		{
-			oos = new ObjectOutputStream(out);
+			this.outputStream = outputStream;
+			oos = new ObjectOutputStream(outputStream);
 		}
 
 		@Override
@@ -254,11 +256,11 @@ public class JavaSerializer implements ISerializer
 			}
 			catch (NotSerializableException nsx)
 			{
-				if (ObjectChecker.isAvailable())
+				if (CheckingObjectOutputStream.isAvailable())
 				{
 					// trigger serialization again, but this time gather
 					// some more info
-					new SerializableChecker(nsx).writeObject(obj);
+					new SerializableChecker(outputStream, nsx).writeObject(obj);
 					// if we get here, we didn't fail, while we
 					// should;
 					throw nsx;
@@ -284,58 +286,4 @@ 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/48350b07/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
index b40eac5..a38e756 100644
--- 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
@@ -21,6 +21,7 @@ import java.io.ObjectOutputStream;
 import java.io.OutputStream;
 
 import org.apache.wicket.WicketTestCase;
+import org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream;
 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;
@@ -49,7 +50,7 @@ public class JavaSerializerTest extends WicketTestCase
 			protected ObjectOutputStream newObjectOutputStream(OutputStream out) throws IOException
 			{
 				IObjectChecker checker = new NotDetachedModelChecker();
-				return new ObjectCheckerObjectOutputStream(out, checker);
+				return new CheckingObjectOutputStream(out, checker);
 			}
 		};
 
@@ -106,10 +107,10 @@ public class JavaSerializerTest extends WicketTestCase
 			@Override
 			protected ObjectOutputStream newObjectOutputStream(OutputStream out) throws IOException
 			{
-				return new ObjectCheckerObjectOutputStream(out);
+				return new CheckingObjectOutputStream(out);
 			}
 		};
 		byte[] bytes = serializer.serialize("Something to serialize");
-		System.err.println("bytes: " + bytes.length);
+		assertEquals(57, bytes.length);
 	}
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/48350b07/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 642cf66..879dc75 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.core.util.io.SerializableChecker;
-import org.apache.wicket.core.util.objects.checker.ObjectChecker;
+import org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream;
 import org.apache.wicket.util.Log4jEventHistory;
 import org.apache.wicket.util.value.ValueMap;
 import org.junit.Assert;
@@ -44,7 +44,7 @@ public class SerializableCheckerTest extends Assert
 	@Test
 	public void valueMap() throws IOException
 	{
-		SerializableChecker checker = new SerializableChecker(new NotSerializableException());
+		SerializableChecker checker = new SerializableChecker(new ByteArrayOutputStream(), new NotSerializableException());
 		checker.writeObject(new ValueMap());
 	}
 
@@ -58,12 +58,12 @@ public class SerializableCheckerTest extends Assert
 	@Test
 	public void runtimeExceptionTolerance() throws IOException
 	{
-		Logger logger = LogManager.getLogger(ObjectChecker.class);
+		Logger logger = LogManager.getLogger(CheckingObjectOutputStream.class);
 		logger.setLevel(Level.WARN);
 		Log4jEventHistory logHistory = new Log4jEventHistory();
 		logger.addAppender(logHistory);
-		SerializableChecker serializableChecker = new SerializableChecker(
-			new NotSerializableException());
+		SerializableChecker serializableChecker = new SerializableChecker(new ByteArrayOutputStream(),
+				new NotSerializableException());
 		try
 		{
 			serializableChecker.writeObject(new TestType1());
@@ -82,14 +82,14 @@ public class SerializableCheckerTest extends Assert
 	@Test
 	public void nonSerializableTypeDetection() throws IOException
 	{
-		SerializableChecker serializableChecker = new SerializableChecker(
+		SerializableChecker serializableChecker = new SerializableChecker(new ByteArrayOutputStream(),
 				new NotSerializableException());
 		String exceptionMessage = null;
 		try
 		{
 			serializableChecker.writeObject(new TestType2());
 		}
-		catch (ObjectChecker.ObjectCheckException e)
+		catch (CheckingObjectOutputStream.ObjectCheckException e)
 		{
 			exceptionMessage = e.getMessage();
 		}