You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by da...@apache.org on 2015/05/29 19:11:38 UTC
wicket git commit: WICKET-5916 StackOverflowError in
LoadableDetachableModel
Repository: wicket
Updated Branches:
refs/heads/master 3dd37b3fb -> def03addf
WICKET-5916 StackOverflowError in LoadableDetachableModel
This fixes a circular calling issue with LoadableDetachableModel where
getObject() is called from the context of a load(). By tracking the
state of the LoadableDetachableModel more closely, we can short-circuit
the call chain and break out of the infinite calling loop.
I've opted to use an enum instead of adding a new flag to keep the
amount of flags to a minimum and to keep the memory requirements
low (instead of 2 flags, just one enum is referenced). The external
public and protected interfaces are still the same, so I don't
expect any problems within user applications, unless they are
manipulating the attached flag using reflection.
Fixes WICKET-5916
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/def03add
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/def03add
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/def03add
Branch: refs/heads/master
Commit: def03addf20259d294db09b9b8a9ffa5b114565a
Parents: 3dd37b3
Author: Martijn Dashorst <ma...@gmail.com>
Authored: Fri May 29 19:11:28 2015 +0200
Committer: Martijn Dashorst <ma...@gmail.com>
Committed: Fri May 29 19:11:28 2015 +0200
----------------------------------------------------------------------
.../wicket/model/LoadableDetachableModel.java | 65 +++++++++++----
.../model/LoadableDetachableModelTest.java | 87 ++++++++++++++++++++
2 files changed, 138 insertions(+), 14 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/wicket/blob/def03add/wicket-core/src/main/java/org/apache/wicket/model/LoadableDetachableModel.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/model/LoadableDetachableModel.java b/wicket-core/src/main/java/org/apache/wicket/model/LoadableDetachableModel.java
index c172678..e3a7fc0 100644
--- a/wicket-core/src/main/java/org/apache/wicket/model/LoadableDetachableModel.java
+++ b/wicket-core/src/main/java/org/apache/wicket/model/LoadableDetachableModel.java
@@ -23,8 +23,8 @@ import org.slf4j.LoggerFactory;
/**
* Model that makes working with detachable models a breeze. LoadableDetachableModel holds a
- * temporary, transient model object, that is set when {@link #getObject()} is called by
- * calling abstract method 'load', and that will be reset/ set to null on {@link #detach()}.
+ * temporary, transient model object, that is set when {@link #getObject()} is called by calling
+ * abstract method 'load', and that will be reset/ set to null on {@link #detach()}.
*
* A usage example:
*
@@ -60,8 +60,40 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
/** Logger. */
private static final Logger log = LoggerFactory.getLogger(LoadableDetachableModel.class);
+ private enum AttachingState
+ {
+ DETACHED(false, false),
+ ATTACHING(true, false),
+ ATTACHED(true, true);
+
+ private boolean attaching;
+ private boolean attached;
+
+ private AttachingState(boolean attaching, boolean attached)
+ {
+ this.attached = attached;
+ this.attaching = attaching;
+ }
+
+ public boolean isAttached()
+ {
+ return attached;
+ }
+
+ public boolean isAttaching()
+ {
+ return attaching;
+ }
+
+ @Override
+ public String toString()
+ {
+ return name().toLowerCase();
+ }
+ }
+
/** keeps track of whether this model is attached or detached */
- private transient boolean attached = false;
+ private transient AttachingState attached = AttachingState.DETACHED;
/** temporary, transient object. */
private transient T transientModelObject;
@@ -83,7 +115,7 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
public LoadableDetachableModel(T object)
{
this.transientModelObject = object;
- attached = true;
+ attached = AttachingState.ATTACHED;
}
/**
@@ -92,7 +124,7 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
@Override
public void detach()
{
- if (attached)
+ if (attached == AttachingState.ATTACHED)
{
try
{
@@ -100,7 +132,7 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
}
finally
{
- attached = false;
+ attached = AttachingState.DETACHED;
transientModelObject = null;
log.debug("removed transient object for {}, requestCycle {}", this,
@@ -115,8 +147,11 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
@Override
public final T getObject()
{
- if (!attached)
+ if (attached == AttachingState.DETACHED)
{
+ // prevent infinite attachment loops
+ attached = AttachingState.ATTACHING;
+
transientModelObject = load();
if (log.isDebugEnabled())
@@ -125,7 +160,7 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
", requestCycle " + RequestCycle.get());
}
- attached = true;
+ attached = AttachingState.ATTACHED;
onAttach();
}
return transientModelObject;
@@ -138,7 +173,7 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
*/
public final boolean isAttached()
{
- return attached;
+ return attached.isAttached();
}
/**
@@ -147,9 +182,12 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
@Override
public String toString()
{
- StringBuilder sb = new StringBuilder(super.toString());
- sb.append(":attached=").append(attached).append(":tempModelObject=[").append(
- this.transientModelObject).append("]");
+ StringBuilder sb = new StringBuilder(super.toString());
+ sb.append(":attached=")
+ .append(isAttached())
+ .append(":tempModelObject=[")
+ .append(this.transientModelObject)
+ .append("]");
return sb.toString();
}
@@ -187,8 +225,7 @@ public abstract class LoadableDetachableModel<T> implements IModel<T>
@Override
public void setObject(final T object)
{
- attached = true;
+ attached = AttachingState.ATTACHED;
transientModelObject = object;
}
-
}
http://git-wip-us.apache.org/repos/asf/wicket/blob/def03add/wicket-core/src/test/java/org/apache/wicket/model/LoadableDetachableModelTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/model/LoadableDetachableModelTest.java b/wicket-core/src/test/java/org/apache/wicket/model/LoadableDetachableModelTest.java
new file mode 100644
index 0000000..a087a78
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/model/LoadableDetachableModelTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.model;
+
+import static org.hamcrest.core.Is.is;
+
+import org.apache.wicket.WicketTestCase;
+import org.junit.Test;
+
+/**
+ * Tests the states of a LoadableDetachableModel
+ */
+public class LoadableDetachableModelTest extends WicketTestCase
+{
+ /**
+ * Checks whether the LDM can escape recursive calls.
+ */
+ @Test
+ public void recursiveGetObjectDoesntCauseInfiteLoop()
+ {
+ class RecursiveLoad extends LoadableDetachableModel<Integer>
+ {
+ private static final long serialVersionUID = 1L;
+
+ private int count = 0;
+
+ @Override
+ protected Integer load()
+ {
+ count++;
+ getObject();
+ return count;
+ }
+ }
+
+ RecursiveLoad ldm = new RecursiveLoad();
+
+ assertThat(ldm.isAttached(), is(false));
+ assertThat(ldm.getObject(), is(1));
+ assertThat(ldm.isAttached(), is(true));
+ }
+
+ /**
+ * Checks whether the LDM can escape recursive calls.
+ */
+ @Test
+ public void exceptionDuringLoadKeepsLDMDetached()
+ {
+ class ExceptionalLoad extends LoadableDetachableModel<Integer>
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected Integer load()
+ {
+ throw new RuntimeException();
+ }
+ }
+
+ ExceptionalLoad ldm = new ExceptionalLoad();
+
+ assertThat(ldm.isAttached(), is(false));
+ try
+ {
+ assertThat(ldm.getObject(), is(1));
+ fail("shouldn't get here");
+ }
+ catch (RuntimeException e)
+ {
+ assertThat(ldm.isAttached(), is(false));
+ }
+ }
+}