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}