    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}