001// Copyright 2011, 2013 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.http.internal.services; 016 017import java.util.Map; 018import java.util.WeakHashMap; 019import java.util.concurrent.locks.Lock; 020import java.util.concurrent.locks.ReentrantLock; 021import java.util.concurrent.locks.ReentrantReadWriteLock; 022 023import javax.servlet.http.HttpServletRequest; 024import javax.servlet.http.HttpSession; 025 026import org.apache.tapestry5.http.TapestryHttpSymbolConstants; 027import org.apache.tapestry5.http.services.Session; 028import org.apache.tapestry5.http.services.SessionPersistedObjectAnalyzer; 029import org.apache.tapestry5.ioc.annotations.Symbol; 030import org.apache.tapestry5.ioc.services.PerthreadManager; 031 032public class TapestrySessionFactoryImpl implements TapestrySessionFactory 033{ 034 private boolean clustered; 035 036 private final SessionPersistedObjectAnalyzer analyzer; 037 038 private final HttpServletRequest request; 039 040 private final PerthreadManager perthreadManager; 041 042 private final boolean sessionLockingEnabled; 043 044 private final Lock mapLock = new ReentrantLock(); 045 046 private final Map<HttpSession, SessionLock> sessionToLock = new WeakHashMap<HttpSession, SessionLock>(); 047 048 private final SessionLock NO_OP_LOCK = new SessionLock() 049 { 050 public void acquireReadLock() 051 { 052 } 053 054 public void acquireWriteLock() 055 { 056 } 057 }; 058 059 private class SessionLockImpl implements SessionLock 060 { 061 062 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 063 064 private boolean isReadLocked() 065 { 066 return lock.getReadHoldCount() != 0; 067 } 068 069 private boolean isWriteLocked() 070 { 071 return lock.isWriteLockedByCurrentThread(); 072 } 073 074 public void acquireReadLock() 075 { 076 if (isReadLocked() || isWriteLocked()) 077 { 078 return; 079 } 080 081 lock.readLock().lock(); 082 083 perthreadManager.addThreadCleanupCallback(new Runnable() 084 { 085 public void run() 086 { 087 // The read lock may have been released, if upgraded to a write lock. 088 if (isReadLocked()) 089 { 090 lock.readLock().unlock(); 091 } 092 } 093 }); 094 } 095 096 public void acquireWriteLock() 097 { 098 if (isWriteLocked()) 099 { 100 return; 101 } 102 103 if (isReadLocked()) 104 { 105 lock.readLock().unlock(); 106 } 107 108 // During this window, no lock is held, and the next call may block. 109 110 lock.writeLock().lock(); 111 112 perthreadManager.addThreadCleanupCallback(new Runnable() 113 { 114 public void run() 115 { 116 // This is the only way a write lock is unlocked, so no check is needed. 117 lock.writeLock().unlock(); 118 } 119 }); 120 } 121 } 122 123 public TapestrySessionFactoryImpl( 124 @Symbol(TapestryHttpSymbolConstants.CLUSTERED_SESSIONS) 125 boolean clustered, 126 SessionPersistedObjectAnalyzer analyzer, 127 HttpServletRequest request, 128 PerthreadManager perthreadManager, 129 @Symbol(TapestryHttpSymbolConstants.SESSION_LOCKING_ENABLED) 130 boolean sessionLockingEnabled) 131 { 132 this.clustered = clustered; 133 this.analyzer = analyzer; 134 this.request = request; 135 this.perthreadManager = perthreadManager; 136 this.sessionLockingEnabled = sessionLockingEnabled; 137 } 138 139 public Session getSession(boolean create) 140 { 141 final HttpSession httpSession = request.getSession(create); 142 143 if (httpSession == null) 144 { 145 return null; 146 } 147 148 SessionLock lock = lockForSession(httpSession); 149 150 if (clustered) 151 { 152 return new ClusteredSessionImpl(request, httpSession, lock, analyzer); 153 } 154 155 return new SessionImpl(request, httpSession, lock); 156 } 157 158 private SessionLock lockForSession(HttpSession session) 159 { 160 if (!sessionLockingEnabled) 161 { 162 return NO_OP_LOCK; 163 } 164 165 // Because WeakHashMap does not look thread safe to me, we use an exclusive 166 // lock. 167 mapLock.lock(); 168 169 try 170 { 171 SessionLock result = sessionToLock.get(session); 172 173 if (result == null) 174 { 175 result = new SessionLockImpl(); 176 sessionToLock.put(session, result); 177 } 178 179 return result; 180 } finally 181 { 182 mapLock.unlock(); 183 } 184 } 185}