Hi, here we go, new patch. Please comment. I guess most of the changes are quite easily understandable if you've been following this thread, but feel free to ask questions or press me to write more/better comments if anything is unclear. ;-)
Jochen
From 23d657994defa89b86d7a5588d82251b7c1b4752 Mon Sep 17 00:00:00 2001 From: Jochen Kemnade <jkemn...@apache.org> Date: Wed, 28 May 2014 21:12:13 +0200 Subject: [PATCH] Downgrade the global session write lock to a read lock if an immutable object is read from the session --- .../internal/services/ClusteredSessionImpl.java | 9 ++- .../DefaultSessionPersistedObjectAnalyzer.java | 16 +++- .../tapestry5/internal/services/SessionImpl.java | 30 +++++++- .../tapestry5/internal/services/SessionLock.java | 7 +- .../services/TapestrySessionFactoryImpl.java | 33 +++++++-- .../apache/tapestry5/modules/TapestryModule.java | 57 ++++++++++----- .../services/ObjectMutabilityAnalyzer.java | 44 +++++++++++ .../services/SessionPersistedObjectAnalyzer.java | 3 +- .../internal/services/SessionImplTest.java | 85 ++++++++++++++++++++-- .../services/ObjectMutabilityAnalyzerTest.java | 55 ++++++++++++++ 10 files changed, 294 insertions(+), 45 deletions(-) create mode 100644 tapestry-core/src/main/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzer.java create mode 100644 tapestry-core/src/test/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzerTest.java diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java index 3dccb17..88ee5e0 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java @@ -1,4 +1,4 @@ -// Copyright 2011, 2013 The Apache Software Foundation +// Copyright 2011, 2013, 2014 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,11 +15,12 @@ package org.apache.tapestry5.internal.services; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; -import org.apache.tapestry5.ioc.services.PerthreadManager; +import org.apache.tapestry5.services.ObjectMutabilityAnalyzer; import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; + import java.util.Map; /** @@ -43,9 +44,9 @@ public class ClusteredSessionImpl extends SessionImpl public ClusteredSessionImpl(HttpServletRequest request, HttpSession session, - SessionLock lock, SessionPersistedObjectAnalyzer analyzer) + SessionLock lock, SessionPersistedObjectAnalyzer analyzer, ObjectMutabilityAnalyzer objectMutabilityAnalyzer) { - super(request, session, lock); + super(request, session, lock, objectMutabilityAnalyzer); this.analyzer = analyzer; } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java index 280aa12..079f672 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java @@ -1,4 +1,4 @@ -// Copyright 2008, 2011 The Apache Software Foundation +// Copyright 2008, 2011, 2014 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ package org.apache.tapestry5.internal.services; -import org.apache.tapestry5.annotations.ImmutableSessionPersistedObject; +import org.apache.tapestry5.services.ObjectMutabilityAnalyzer; import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer; /** @@ -24,8 +24,16 @@ import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer; */ public class DefaultSessionPersistedObjectAnalyzer implements SessionPersistedObjectAnalyzer<Object> { + private final ObjectMutabilityAnalyzer objectMutabilityAnalyzer; + + public DefaultSessionPersistedObjectAnalyzer( + ObjectMutabilityAnalyzer objectMutabilityAnalyzer) { + this.objectMutabilityAnalyzer = objectMutabilityAnalyzer; + } + /** - * An object is dirty <em>unless</em> it has the {@link org.apache.tapestry5.annotations.ImmutableSessionPersistedObject} + * An object is dirty <em>unless</em> {@link org.apache.tapestry5.services.ObjectMutabilityAnalyzer} + * says differently, e.g. if its class has the {@link org.apache.tapestry5.annotations.ImmutableSessionPersistedObject} * annotation. * * @param sessionPersistedObject to analyze @@ -33,7 +41,7 @@ public class DefaultSessionPersistedObjectAnalyzer implements SessionPersistedOb */ public boolean checkAndResetDirtyState(Object sessionPersistedObject) { - boolean immutable = sessionPersistedObject.getClass().getAnnotation(ImmutableSessionPersistedObject.class) != null; + boolean immutable = objectMutabilityAnalyzer.isImmutable(sessionPersistedObject); // Immutable objects are always clean, others are assumed dirty. // Go implement OptimizedSessionPersistedObject if you don't like it. diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java index 7829285..553c087 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java @@ -1,4 +1,4 @@ -// Copyright 2006-2013 The Apache Software Foundation +// Copyright 2006-2014 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,11 +16,12 @@ package org.apache.tapestry5.internal.services; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.internal.util.InternalUtils; -import org.apache.tapestry5.ioc.services.PerthreadManager; +import org.apache.tapestry5.services.ObjectMutabilityAnalyzer; import org.apache.tapestry5.services.Session; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; + import java.util.Collections; import java.util.Enumeration; import java.util.List; @@ -36,20 +37,41 @@ public class SessionImpl implements Session private boolean invalidated = false; + private boolean mutableObjectWasRead = false; + private final SessionLock lock; - public SessionImpl(HttpServletRequest request, HttpSession session, SessionLock lock) + private final ObjectMutabilityAnalyzer objectMutabilityAnalyzer; + + + + public SessionImpl(HttpServletRequest request, HttpSession session, SessionLock lock, ObjectMutabilityAnalyzer objectMutabilityAnalyzer) { this.request = request; this.session = session; this.lock = lock; + this.objectMutabilityAnalyzer = objectMutabilityAnalyzer; } public Object getAttribute(String name) { + // We acquire the write lock as a precaution because the object that is to be read might be mutable lock.acquireWriteLock(); - return session.getAttribute(name); + Object attributeValue = session.getAttribute(name); + + // If no mutable object was read from the session before, we may be able to release the write lock + if (!mutableObjectWasRead) + { + if (objectMutabilityAnalyzer.isImmutable(attributeValue)) + { + lock.downgradeWriteLockToReadLock(); + } else + { + mutableObjectWasRead = true; + } + } + return attributeValue; } public List<String> getAttributeNames() diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionLock.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionLock.java index d234bd4..cea240f 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionLock.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionLock.java @@ -1,4 +1,4 @@ -// Copyright 2013 The Apache Software Foundation +// Copyright 2013, 2014 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -33,4 +33,9 @@ public interface SessionLock * this may block for a while. Does nothing if the write lock is already held by this thread. */ void acquireWriteLock(); + + /** + * Releases the exclusive write lock and acquires the read lock instead. + */ + void downgradeWriteLockToReadLock(); } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TapestrySessionFactoryImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TapestrySessionFactoryImpl.java index bc221a6..da37d5a 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TapestrySessionFactoryImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TapestrySessionFactoryImpl.java @@ -1,4 +1,4 @@ -// Copyright 2011, 2013 The Apache Software Foundation +// Copyright 2011, 2013, 2014 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,11 +17,13 @@ package org.apache.tapestry5.internal.services; import org.apache.tapestry5.SymbolConstants; import org.apache.tapestry5.ioc.annotations.Symbol; import org.apache.tapestry5.ioc.services.PerthreadManager; +import org.apache.tapestry5.services.ObjectMutabilityAnalyzer; import org.apache.tapestry5.services.Session; import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; + import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.locks.Lock; @@ -34,6 +36,8 @@ public class TapestrySessionFactoryImpl implements TapestrySessionFactory private final SessionPersistedObjectAnalyzer analyzer; + private final ObjectMutabilityAnalyzer objectMutabilityAnalyzer; + private final HttpServletRequest request; private final PerthreadManager perthreadManager; @@ -53,6 +57,10 @@ public class TapestrySessionFactoryImpl implements TapestrySessionFactory public void acquireWriteLock() { } + + public void downgradeWriteLockToReadLock() + { + } }; private class SessionLockImpl implements SessionLock @@ -112,17 +120,31 @@ public class TapestrySessionFactoryImpl implements TapestrySessionFactory { public void run() { - // This is the only way a write lock is unlocked, so no check is needed. - lock.writeLock().unlock(); + // The write lock may have been released, if downgraded to a read lock. + if (isWriteLocked()) + { + lock.writeLock().unlock(); + } } }); } + + @Override + public void downgradeWriteLockToReadLock() + { + lock.writeLock().unlock(); + + // During this window, no lock is held, and the next call may block. + + acquireReadLock(); + } } public TapestrySessionFactoryImpl( @Symbol(SymbolConstants.CLUSTERED_SESSIONS) boolean clustered, SessionPersistedObjectAnalyzer analyzer, + ObjectMutabilityAnalyzer objectMutabilityAnalyzer, HttpServletRequest request, PerthreadManager perthreadManager, @Symbol(SymbolConstants.SESSION_LOCKING_ENABLED) @@ -130,6 +152,7 @@ public class TapestrySessionFactoryImpl implements TapestrySessionFactory { this.clustered = clustered; this.analyzer = analyzer; + this.objectMutabilityAnalyzer = objectMutabilityAnalyzer; this.request = request; this.perthreadManager = perthreadManager; this.sessionLockingEnabled = sessionLockingEnabled; @@ -148,10 +171,10 @@ public class TapestrySessionFactoryImpl implements TapestrySessionFactory if (clustered) { - return new ClusteredSessionImpl(request, httpSession, lock, analyzer); + return new ClusteredSessionImpl(request, httpSession, lock, analyzer, objectMutabilityAnalyzer); } - return new SessionImpl(request, httpSession, lock); + return new SessionImpl(request, httpSession, lock, objectMutabilityAnalyzer); } private SessionLock lockForSession(HttpSession session) diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java index e520b09..196aea9 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java @@ -94,6 +94,7 @@ import org.slf4j.Logger; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import java.io.IOException; import java.lang.annotation.Annotation; import java.math.BigDecimal; @@ -2323,29 +2324,16 @@ public final class TapestryModule } /** - * Identifies String, Number and Boolean as immutable objects, a catch-all - * handler for Object (that understands - * the {@link org.apache.tapestry5.annotations.ImmutableSessionPersistedObject} annotation), - * and a handler for {@link org.apache.tapestry5.OptimizedSessionPersistedObject}. + * Uses ObjectMutabilityAnalyzer to determine if an object is immutable, and adds + * a handler for {@link org.apache.tapestry5.OptimizedSessionPersistedObject}. * * @since 5.1.0.0 */ public static void contributeSessionPersistedObjectAnalyzer( - MappedConfiguration<Class, SessionPersistedObjectAnalyzer> configuration) + MappedConfiguration<Class, SessionPersistedObjectAnalyzer> configuration, + ObjectMutabilityAnalyzer objectMutabilityAnalyzer) { - configuration.add(Object.class, new DefaultSessionPersistedObjectAnalyzer()); - - SessionPersistedObjectAnalyzer<Object> immutable = new SessionPersistedObjectAnalyzer<Object>() - { - public boolean checkAndResetDirtyState(Object sessionPersistedObject) - { - return false; - } - }; - - configuration.add(String.class, immutable); - configuration.add(Number.class, immutable); - configuration.add(Boolean.class, immutable); + configuration.add(Object.class, new DefaultSessionPersistedObjectAnalyzer(objectMutabilityAnalyzer)); configuration.add(OptimizedSessionPersistedObject.class, new OptimizedSessionPersistedObjectAnalyzer()); } @@ -2654,4 +2642,37 @@ public final class TapestryModule { return strategyBuilder.build(ValueLabelProvider.class, configuration); } + + /** + * @since 5.4 + */ + @Marker(Primary.class) + public ObjectMutabilityAnalyzer buildObjectMutabilityAnalyzer(List<ObjectMutabilityAnalyzer> configuration) + { + return chainBuilder.build(ObjectMutabilityAnalyzer.class, configuration); + } + + @Contribute(ObjectMutabilityAnalyzer.class) + public static void addCodeMutabilityAnalyzers(OrderedConfiguration<ObjectMutabilityAnalyzer> configuration){ + configuration.add("null", new ObjectMutabilityAnalyzer(){ + public boolean isImmutable(Object object) { + return object == null; + } + }, OrderConstraintBuilder.beforeAll().build()); + + configuration.add("ImmutableClass", new ObjectMutabilityAnalyzer(){ + private final Set<Class> immutableClasses = Collections.unmodifiableSet( + CollectionFactory.<Class, Class>newSet(String.class, Character.class, Byte.class, + Integer.class, Long.class, Short.class, Double.class, Boolean.class)); + public boolean isImmutable(Object object) { + return immutableClasses.contains(object.getClass()); + } + }); + + configuration.add("ImmutableSessionPersistedObject", new ObjectMutabilityAnalyzer(){ + public boolean isImmutable(Object object) { + return object.getClass().getAnnotation(ImmutableSessionPersistedObject.class) != null; + } + }); + } } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzer.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzer.java new file mode 100644 index 0000000..7141be0 --- /dev/null +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzer.java @@ -0,0 +1,44 @@ +// Copyright 2014 The Apache Software Foundation +// +// Licensed 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.tapestry5.services; + +import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration; +import org.apache.tapestry5.ioc.services.ChainBuilder; + +/** + * Analyzes an object for mutability. If an object is immutable, some optimizations can be made + * e.g. when storing it as a HTTP session attribute. + * <p/> + * The service implementation is a {@linkplain ChainBuilder + * chain of command}. The service may be + * contributed to using the {@link org.apache.tapestry5.ioc.annotations.Primary} marker annotation. + * + * @see org.apache.tapestry5.annotations.ImmutableSessionPersistedObject + * @see org.apache.tapestry5.OptimizedSessionPersistedObject + * @see org.apache.tapestry5.internal.services.SessionLock + * @since 5.4 + */ +@UsesOrderedConfiguration(ObjectMutabilityAnalyzer.class) +public interface ObjectMutabilityAnalyzer +{ + /** + * Check if an object is immutable + * @param object the object to analyze + * @return {@code true} if the object is immutable + * @since 5.4 + */ + boolean isImmutable(Object object); + +} diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java index 950fbeb..83163c4 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java @@ -1,4 +1,4 @@ -// Copyright 2008, 2011 The Apache Software Foundation +// Copyright 2008, 2011, 2014 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration; * * @see org.apache.tapestry5.annotations.ImmutableSessionPersistedObject * @see org.apache.tapestry5.OptimizedSessionPersistedObject + * @see org.apache.tapestry5.services.ObjectMutabilityAnalyzer * @since 5.1.0.0 */ @UsesMappedConfiguration(key = Class.class, value = SessionPersistedObjectAnalyzer.class) diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java index 4f1aebf..6873c3f 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java @@ -1,4 +1,4 @@ -// Copyright 2006-2013 The Apache Software Foundation +// Copyright 2006-2014 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,12 +15,14 @@ package org.apache.tapestry5.internal.services; import org.apache.tapestry5.internal.test.InternalBaseTestCase; +import org.apache.tapestry5.services.ObjectMutabilityAnalyzer; import org.apache.tapestry5.services.Session; import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer; import org.testng.annotations.Test; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; + import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -33,19 +35,25 @@ public class SessionImplTest extends InternalBaseTestCase return newMock(SessionLock.class); } + private ObjectMutabilityAnalyzer mockObjectMutabilityAnalyzer() + { + return newMock(ObjectMutabilityAnalyzer.class); + } + @Test public void get_attribute_names() { Enumeration e = Collections.enumeration(Arrays.asList("fred", "barney")); HttpSession hs = mockHttpSession(); SessionLock lock = mockLock(); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); lock.acquireReadLock(); expect(hs.getAttributeNames()).andReturn(e); replay(); - Session session = new SessionImpl(null, hs, lock); + Session session = new SessionImpl(null, hs, lock, mutabilityAnalyzer); assertEquals(session.getAttributeNames(), Arrays.asList("barney", "fred")); @@ -58,6 +66,7 @@ public class SessionImplTest extends InternalBaseTestCase Enumeration e = Collections.enumeration(Arrays.asList("fred", "barney", "fanny")); HttpSession hs = mockHttpSession(); SessionLock lock = mockLock(); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); lock.acquireReadLock(); @@ -65,7 +74,7 @@ public class SessionImplTest extends InternalBaseTestCase replay(); - Session session = new SessionImpl(null, hs, lock); + Session session = new SessionImpl(null, hs, lock, mutabilityAnalyzer); assertEquals(session.getAttributeNames("f"), Arrays.asList("fanny", "fred")); @@ -77,12 +86,13 @@ public class SessionImplTest extends InternalBaseTestCase { HttpSession hs = mockHttpSession(); SessionLock lock = mockLock(); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); hs.invalidate(); replay(); - Session session = new SessionImpl(null, hs, lock); + Session session = new SessionImpl(null, hs, lock, mutabilityAnalyzer); session.invalidate(); @@ -95,12 +105,13 @@ public class SessionImplTest extends InternalBaseTestCase HttpSession hs = mockHttpSession(); HttpServletRequest hsr = mockHttpServletRequest(); SessionLock lock = mockLock(); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); train_getSession(hsr, false, hs); replay(); - Session session = new SessionImpl(hsr, hs, lock); + Session session = new SessionImpl(hsr, hs, lock, mutabilityAnalyzer); assertFalse(session.isInvalidated()); @@ -126,13 +137,14 @@ public class SessionImplTest extends InternalBaseTestCase public void set_max_inactive() { HttpSession hs = mockHttpSession(); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); int seconds = 999; hs.setMaxInactiveInterval(seconds); replay(); - Session session = new SessionImpl(null, hs, null); + Session session = new SessionImpl(null, hs, null, mutabilityAnalyzer); session.setMaxInactiveInterval(seconds); @@ -143,6 +155,7 @@ public class SessionImplTest extends InternalBaseTestCase public void get_max_inactive() { HttpSession hs = mockHttpSession(); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); int seconds = 999; @@ -150,7 +163,7 @@ public class SessionImplTest extends InternalBaseTestCase replay(); - Session session = new SessionImpl(null, hs, null); + Session session = new SessionImpl(null, hs, null, mutabilityAnalyzer); assertEquals(session.getMaxInactiveInterval(), seconds); @@ -163,16 +176,18 @@ public class SessionImplTest extends InternalBaseTestCase HttpSession hs = mockHttpSession(); HttpServletRequest hsr = mockHttpServletRequest(); SessionPersistedObjectAnalyzer analyzer = newMock(SessionPersistedObjectAnalyzer.class); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); Object dirty = new Object(); SessionLock lock = mockLock(); lock.acquireWriteLock(); train_getAttribute(hs, "dirty", dirty); + expect(mutabilityAnalyzer.isImmutable(dirty)).andReturn(false); replay(); - Session session = new ClusteredSessionImpl(hsr, hs, lock, analyzer); + Session session = new ClusteredSessionImpl(hsr, hs, lock, analyzer, mutabilityAnalyzer); assertSame(session.getAttribute("dirty"), dirty); @@ -192,4 +207,58 @@ public class SessionImplTest extends InternalBaseTestCase verify(); } + + @Test + public void reading_an_immutable_object_releases_the_write_lock() + { + HttpSession hs = mockHttpSession(); + HttpServletRequest hsr = mockHttpServletRequest(); + SessionPersistedObjectAnalyzer analyzer = newMock(SessionPersistedObjectAnalyzer.class); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); + Object immutable = new Object(); + SessionLock lock = mockLock(); + + lock.acquireWriteLock(); + + train_getAttribute(hs, "immutable", immutable); + expect(mutabilityAnalyzer.isImmutable(immutable)).andReturn(true); + lock.downgradeWriteLockToReadLock(); + replay(); + + Session session = new ClusteredSessionImpl(hsr, hs, lock, analyzer, mutabilityAnalyzer); + + assertSame(session.getAttribute("immutable"), immutable); + + verify(); + + } + + @Test + public void reading_a_mutable_and_an_immutable_object_keeps_the_write_lock() + { + HttpSession hs = mockHttpSession(); + HttpServletRequest hsr = mockHttpServletRequest(); + SessionPersistedObjectAnalyzer analyzer = newMock(SessionPersistedObjectAnalyzer.class); + ObjectMutabilityAnalyzer mutabilityAnalyzer = mockObjectMutabilityAnalyzer(); + Object immutable = new Object(); + Object mutable = new Object(); + SessionLock lock = mockLock(); + + lock.acquireWriteLock(); + + train_getAttribute(hs, "mutable", mutable); + expect(mutabilityAnalyzer.isImmutable(mutable)).andReturn(false); + + train_getAttribute(hs, "immutable", immutable); + lock.acquireWriteLock(); + replay(); + + Session session = new ClusteredSessionImpl(hsr, hs, lock, analyzer, mutabilityAnalyzer); + + assertSame(session.getAttribute("mutable"), mutable); + assertSame(session.getAttribute("immutable"), immutable); + + verify(); + + } } diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzerTest.java new file mode 100644 index 0000000..d930648 --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/services/ObjectMutabilityAnalyzerTest.java @@ -0,0 +1,55 @@ +// Copyright 2014 The Apache Software Foundation +// +// Licensed 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.tapestry5.services; + +import java.util.HashMap; + +import org.apache.tapestry5.annotations.ImmutableSessionPersistedObject; +import org.apache.tapestry5.internal.test.InternalBaseTestCase; +import org.testng.annotations.Test; + +public class ObjectMutabilityAnalyzerTest extends InternalBaseTestCase +{ + @Test + public void mutable_objects() + { + + ObjectMutabilityAnalyzer analyzer = getService(ObjectMutabilityAnalyzer.class); + + assertFalse(analyzer.isImmutable(new HashMap())); + assertFalse(analyzer.isImmutable(new String[]{"foo"})); + + } + + @Test + public void immutable_objects() + { + + ObjectMutabilityAnalyzer analyzer = getService(ObjectMutabilityAnalyzer.class); + + assertTrue(analyzer.isImmutable(true)); + assertTrue(analyzer.isImmutable(Boolean.TRUE)); + assertTrue(analyzer.isImmutable(null)); + assertTrue(analyzer.isImmutable("Hello World")); + assertTrue(analyzer.isImmutable(42)); + assertTrue(analyzer.isImmutable(new ImmutableObject())); + + } + + @ImmutableSessionPersistedObject + private class ImmutableObject { + + } +} -- 1.9.1
signature.asc
Description: OpenPGP digital signature