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}