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.internal.services; 016 017import org.apache.tapestry5.SymbolConstants; 018import org.apache.tapestry5.ioc.annotations.Symbol; 019import org.apache.tapestry5.ioc.services.PerthreadManager; 020import org.apache.tapestry5.services.Session; 021import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer; 022 023import javax.servlet.http.HttpServletRequest; 024import javax.servlet.http.HttpSession; 025import java.util.Map; 026import java.util.WeakHashMap; 027import java.util.concurrent.locks.Lock; 028import java.util.concurrent.locks.ReentrantLock; 029import java.util.concurrent.locks.ReentrantReadWriteLock; 030 031public class TapestrySessionFactoryImpl implements TapestrySessionFactory 032{ 033 private boolean clustered; 034 035 private final SessionPersistedObjectAnalyzer analyzer; 036 037 private final HttpServletRequest request; 038 039 private final PerthreadManager perthreadManager; 040 041 private final boolean sessionLockingEnabled; 042 043 private final Lock mapLock = new ReentrantLock(); 044 045 private final Map<HttpSession, SessionLock> sessionToLock = new WeakHashMap<HttpSession, SessionLock>(); 046 047 private final SessionLock NO_OP_LOCK = new SessionLock() 048 { 049 public void acquireReadLock() 050 { 051 } 052 053 public void acquireWriteLock() 054 { 055 } 056 }; 057 058 private class SessionLockImpl implements SessionLock 059 { 060 061 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 062 063 private boolean isReadLocked() 064 { 065 return lock.getReadHoldCount() != 0; 066 } 067 068 private boolean isWriteLocked() 069 { 070 return lock.isWriteLockedByCurrentThread(); 071 } 072 073 public void acquireReadLock() 074 { 075 if (isReadLocked() || isWriteLocked()) 076 { 077 return; 078 } 079 080 lock.readLock().lock(); 081 082 perthreadManager.addThreadCleanupCallback(new Runnable() 083 { 084 public void run() 085 { 086 // The read lock may have been released, if upgraded to a write lock. 087 if (isReadLocked()) 088 { 089 lock.readLock().unlock(); 090 } 091 } 092 }); 093 } 094 095 public void acquireWriteLock() 096 { 097 if (isWriteLocked()) 098 { 099 return; 100 } 101 102 if (isReadLocked()) 103 { 104 lock.readLock().unlock(); 105 } 106 107 // During this window, no lock is held, and the next call may block. 108 109 lock.writeLock().lock(); 110 111 perthreadManager.addThreadCleanupCallback(new Runnable() 112 { 113 public void run() 114 { 115 // This is the only way a write lock is unlocked, so no check is needed. 116 lock.writeLock().unlock(); 117 } 118 }); 119 } 120 } 121 122 public TapestrySessionFactoryImpl( 123 @Symbol(SymbolConstants.CLUSTERED_SESSIONS) 124 boolean clustered, 125 SessionPersistedObjectAnalyzer analyzer, 126 HttpServletRequest request, 127 PerthreadManager perthreadManager, 128 @Symbol(SymbolConstants.SESSION_LOCKING_ENABLED) 129 boolean sessionLockingEnabled) 130 { 131 this.clustered = clustered; 132 this.analyzer = analyzer; 133 this.request = request; 134 this.perthreadManager = perthreadManager; 135 this.sessionLockingEnabled = sessionLockingEnabled; 136 } 137 138 public Session getSession(boolean create) 139 { 140 final HttpSession httpSession = request.getSession(create); 141 142 if (httpSession == null) 143 { 144 return null; 145 } 146 147 SessionLock lock = lockForSession(httpSession); 148 149 if (clustered) 150 { 151 return new ClusteredSessionImpl(request, httpSession, lock, analyzer); 152 } 153 154 return new SessionImpl(request, httpSession, lock); 155 } 156 157 private SessionLock lockForSession(HttpSession session) 158 { 159 if (!sessionLockingEnabled) 160 { 161 return NO_OP_LOCK; 162 } 163 164 // Because WeakHashMap does not look thread safe to me, we use an exclusive 165 // lock. 166 mapLock.lock(); 167 168 try 169 { 170 SessionLock result = sessionToLock.get(session); 171 172 if (result == null) 173 { 174 result = new SessionLockImpl(); 175 sessionToLock.put(session, result); 176 } 177 178 return result; 179 } finally 180 { 181 mapLock.unlock(); 182 } 183 } 184}