    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}