Package com.hazelcast.web

Source Code of com.hazelcast.web.WebFilter$HazelcastHttpSession

/*
* Copyright (c) 2008-2014, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hazelcast.web;

import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryListener;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.core.MapEvent;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;

/**
* Provides clustered sessions by backing session data with an {@link IMap}.
* <p/>
* Using this filter requires also registering a {@link SessionListener} to provide session timeout notifications.
* Failure to register the listener when using this filter will result in session state getting out of sync between
* the servlet container and Hazelcast.
* <p/>
* This filter supports the following {@code &lt;init-param&gt;} values:
* <ul>
* <li>{@code use-client}: When enabled, a {@link com.hazelcast.client.HazelcastClient HazelcastClient} is
* used to connect to the cluster, rather than joining as a full node. (Default: {@code false})</li>
* <li>{@code config-location}: Specifies the location of an XML configuration file that can be used to
* initialize the {@link HazelcastInstance} (Default: None; the {@link HazelcastInstance} is initialized
* using its own defaults)</li>
* <li>{@code client-config-location}: Specifies the location of an XML configuration file that can be
* used to initialize the {@link HazelcastInstance}. <i>This setting is only checked when {@code use-client}
* is set to {@code true}.</i> (Default: Falls back on {@code config-location})</li>
* <li>{@code instance-name}: Names the {@link HazelcastInstance}. This can be used to reference an already-
* initialized {@link HazelcastInstance} in the same JVM (Default: The configured instance name, or a
* generated name if the configuration does not specify a value)</li>
* <li>{@code shutdown-on-destroy}: When enabled, shuts down the {@link HazelcastInstance} when the filter is
* destroyed (Default: {@code true})</li>
* <li>{@code map-name}: Names the {@link IMap} the filter should use to persist session details (Default:
* {@code "_web_" + ServletContext.getServletContextName()}; e.g. "_web_MyApp")</li>
* <li>{@code session-ttl-seconds}: Sets the {@link MapConfig#setTimeToLiveSeconds(int) time-to-live} for
* the {@link IMap} used to persist session details (Default: Uses the existing {@link MapConfig} setting
* for the {@link IMap}, which defaults to infinite)</li>
* <li>{@code sticky-session}: When enabled, optimizes {@link IMap} interactions by assuming individual sessions
* are only used from a single node (Default: {@code true})</li>
* <li>{@code deferred-write}: When enabled, optimizes {@link IMap} interactions by only writing session attributes
* at the end of a request. This can yield significant performance improvements for session-heavy applications
* (Default: {@code false})</li>
* <li>{@code cookie-name}: Sets the name for the Hazelcast session cookie (Default:
* {@link #HAZELCAST_SESSION_COOKIE_NAME "hazelcast.sessionId"}</li>
* <li>{@code cookie-domain}: Sets the domain for the Hazelcast session cookie (Default: {@code null})</li>
* <li>{@code cookie-secure}: When enabled, indicates the Hazelcast session cookie should only be sent over
* secure protocols (Default: {@code false})</li>
* <li>{@code cookie-http-only}: When enabled, marks the Hazelcast session cookie as "HttpOnly", indicating
* it should not be available to scripts (Default: {@code false})
* <ul>
* <li>{@code cookie-http-only} requires a Servlet 3.0-compatible container, such as Tomcat 7+ or Jetty 8+</li>
* </ul>
* </li>
* </ul>
*/
public class WebFilter implements Filter {

    public static final String WEB_FILTER_ATTRIBUTE_KEY = WebFilter.class.getName();

    protected static final String HAZELCAST_SESSION_ATTRIBUTE_SEPARATOR = "::hz::";

    protected static final ILogger LOGGER = Logger.getLogger(WebFilter.class);
    protected static final LocalCacheEntry NULL_ENTRY = new LocalCacheEntry();
    protected static final String HAZELCAST_REQUEST = "*hazelcast-request";
    protected static final String HAZELCAST_SESSION_COOKIE_NAME = "hazelcast.sessionId";

    protected ServletContext servletContext;
    protected FilterConfig filterConfig;

    private final ConcurrentMap<String, String> originalSessions = new ConcurrentHashMap<String, String>(1000);
    private final ConcurrentMap<String, HazelcastHttpSession> sessions =
            new ConcurrentHashMap<String, HazelcastHttpSession>(1000);

    private String sessionCookieName = HAZELCAST_SESSION_COOKIE_NAME;
    private HazelcastInstance hazelcastInstance;
    private String clusterMapName = "none";
    private String sessionCookieDomain;
    private boolean sessionCookieSecure;
    private boolean sessionCookieHttpOnly;
    private boolean stickySession = true;
    private boolean shutdownOnDestroy = true;
    private boolean deferredWrite;
    private Properties properties;

    public WebFilter() {
    }

    public WebFilter(Properties properties) {
        this.properties = properties;
    }

    void destroyOriginalSession(HttpSession originalSession) {
        String hazelcastSessionId = originalSessions.remove(originalSession.getId());
        if (hazelcastSessionId != null) {
            HazelcastHttpSession hazelSession = sessions.remove(hazelcastSessionId);
            if (hazelSession != null) {
                destroySession(hazelSession, false);
            }
        }
    }

    private static String generateSessionId() {
        final String id = UUID.randomUUID().toString();
        final StringBuilder sb = new StringBuilder("HZ");
        final char[] chars = id.toCharArray();
        for (final char c : chars) {
            if (c != '-') {
                if (Character.isLetter(c)) {
                    sb.append(Character.toUpperCase(c));
                } else {
                    sb.append(c);
                }
            }
        }
        return sb.toString();
    }

    public final void init(final FilterConfig config)
            throws ServletException {
        filterConfig = config;

        // Register the WebFilter with the ServletContext so SessionListener can look it up. The name
        // here is WebFilter.class instead of getClass() because WebFilter can have subclasses
        servletContext = config.getServletContext();
        servletContext.setAttribute(WEB_FILTER_ATTRIBUTE_KEY, this);

        initInstance();
        initCookieParams();
        initParams();

        String mapName = getParam("map-name");
        if (mapName != null) {
            clusterMapName = mapName;
        } else {
            clusterMapName = "_web_" + servletContext.getServletContextName();
        }

        try {
            String sessionTTL = getParam("session-ttl-seconds");
            if (sessionTTL != null) {
                Config hzConfig = hazelcastInstance.getConfig();

                MapConfig mapConfig = hzConfig.getMapConfig(clusterMapName);
                mapConfig.setTimeToLiveSeconds(Integer.parseInt(sessionTTL));

                hzConfig.addMapConfig(mapConfig);
            }
        } catch (UnsupportedOperationException ignored) {
            LOGGER.info("client cannot access Config.");
        }

        if (!stickySession) {
            getClusterMap().addEntryListener(new EntryListener<String, Object>() {
                public void entryAdded(EntryEvent<String, Object> entryEvent) {
                }

                public void entryRemoved(EntryEvent<String, Object> entryEvent) {
                    if (entryEvent.getMember() == null || !entryEvent.getMember().localMember()) {
                        removeSessionLocally(entryEvent.getKey());
                    }
                }

                public void entryUpdated(EntryEvent<String, Object> entryEvent) {
                }

                public void entryEvicted(EntryEvent<String, Object> entryEvent) {
                    entryRemoved(entryEvent);
                }

                @Override
                public void mapEvicted(MapEvent event) {
                    // this method should be updated if we internally call evictAll in session replication logic
                }

                @Override
                public void mapCleared(MapEvent event) {
                    // this method should be updated if we internally call clearAll in session replication logic
                }
            }, false);
        }

        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest("sticky:" + stickySession + ", shutdown-on-destroy: " + shutdownOnDestroy
                    + ", map-name: " + clusterMapName);
        }
    }

    private void initParams() {
        String stickySessionParam = getParam("sticky-session");
        if (stickySessionParam != null) {
            stickySession = Boolean.valueOf(stickySessionParam);
        }
        String shutdownOnDestroyParam = getParam("shutdown-on-destroy");
        if (shutdownOnDestroyParam != null) {
            shutdownOnDestroy = Boolean.valueOf(shutdownOnDestroyParam);
        }
        String deferredWriteParam = getParam("deferred-write");
        if (deferredWriteParam != null) {
            deferredWrite = Boolean.parseBoolean(deferredWriteParam);
        }
    }

    private void initCookieParams() {
        String cookieName = getParam("cookie-name");
        if (cookieName != null) {
            sessionCookieName = cookieName;
        }
        String cookieDomain = getParam("cookie-domain");
        if (cookieDomain != null) {
            sessionCookieDomain = cookieDomain;
        }
        String cookieSecure = getParam("cookie-secure");
        if (cookieSecure != null) {
            sessionCookieSecure = Boolean.valueOf(cookieSecure);
        }
        String cookieHttpOnly = getParam("cookie-http-only");
        if (cookieHttpOnly != null) {
            sessionCookieHttpOnly = Boolean.valueOf(cookieHttpOnly);
        }
    }

    private void initInstance() throws ServletException {
        if (properties == null) {
            properties = new Properties();
        }
        setProperty(HazelcastInstanceLoader.CONFIG_LOCATION);
        setProperty(HazelcastInstanceLoader.INSTANCE_NAME);
        setProperty(HazelcastInstanceLoader.USE_CLIENT);
        setProperty(HazelcastInstanceLoader.CLIENT_CONFIG_LOCATION);
        hazelcastInstance = getInstance(properties);
    }

    private void setProperty(String propertyName) {
        String value = getParam(propertyName);
        if (value != null) {
            properties.setProperty(propertyName, value);
        }
    }

    private void removeSessionLocally(String sessionId) {
        HazelcastHttpSession hazelSession = sessions.remove(sessionId);
        if (hazelSession != null) {
            originalSessions.remove(hazelSession.originalSession.getId());
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.finest("Destroying session locally " + hazelSession);
            }
            hazelSession.destroy();
        }
    }

    private String extractAttributeKey(String key) {
        return key.substring(key.indexOf(HAZELCAST_SESSION_ATTRIBUTE_SEPARATOR) + HAZELCAST_SESSION_ATTRIBUTE_SEPARATOR.length());
    }

    protected HazelcastHttpSession createNewSession(RequestWrapper requestWrapper, String existingSessionId) {
        String id = existingSessionId == null ? generateSessionId() : existingSessionId;
        if (requestWrapper.getOriginalSession(false) != null) {
            LOGGER.finest("Original session exists!!!");
        }

        HttpSession originalSession = requestWrapper.getOriginalSession(true);
        HazelcastHttpSession hazelcastSession = createHazelcastHttpSession(id, originalSession, deferredWrite);
        if (existingSessionId == null) {
            hazelcastSession.setClusterWideNew(true);
            // If the session is being created for the first time, add its initial reference in the cluster-wide map.
            getClusterMap().executeOnKey(id, new AddSessionEntryProcessor());
        }

        updateSessionMaps(id, originalSession, hazelcastSession);
        addSessionCookie(requestWrapper, id);

        return hazelcastSession;
    }

    /**
     * {@code HazelcastHttpSession instance} creation is split off to a separate method to allow subclasses to return a
     * customized / extended version of {@code HazelcastHttpSession}.
     *
     * @param id the session id
     * @param originalSession the original session
     * @param deferredWrite whether writes are deferred
     * @return a new HazelcastHttpSession instance
     */
    protected HazelcastHttpSession createHazelcastHttpSession(String id, HttpSession originalSession, boolean deferredWrite) {
        return new HazelcastHttpSession(id, originalSession, deferredWrite);
    }

    private void prepareReloadingSession(HazelcastHttpSession hazelcastSession) {
        if (deferredWrite && hazelcastSession != null) {
            Map<String, LocalCacheEntry> cache = hazelcastSession.localCache;
            for (LocalCacheEntry cacheEntry : cache.values()) {
                cacheEntry.reload = true;
            }
        }
    }

    private void updateSessionMaps(String sessionId, HttpSession originalSession, HazelcastHttpSession hazelcastSession) {
        sessions.put(hazelcastSession.getId(), hazelcastSession);
        String oldHazelcastSessionId = originalSessions.put(originalSession.getId(), hazelcastSession.getId());
        if (LOGGER.isFinestEnabled()) {
            if (oldHazelcastSessionId != null) {
                LOGGER.finest("!!! Overwrote an existing hazelcastSessionId " + oldHazelcastSessionId);
            }
            LOGGER.finest("Created new session with id: " + sessionId);
            LOGGER.finest(sessions.size() + " is sessions.size and originalSessions.size: " + originalSessions.size());
        }
    }

    /**
     * Destroys a session, determining if it should be destroyed clusterwide automatically or via expiry.
     *
     * @param session    the session to be destroyed <i>locally</i>
     * @param invalidate {@code true} if the session has been invalidated and should be destroyed on all nodes
     *                   in the cluster; otherwise, {@code false} to only remove the session globally if this
     *                   node was the final node referencing it
     */
    protected void destroySession(HazelcastHttpSession session, boolean invalidate) {
        if (LOGGER.isFinestEnabled()) {
            LOGGER.finest("Destroying local session: " + session.getId());
        }
        sessions.remove(session.getId());
        originalSessions.remove(session.originalSession.getId());
        session.destroy();

        final IMap<String, Object> clusterMap = getClusterMap();
        final boolean invalidated;
        if (invalidate) {
            if (LOGGER.isFinestEnabled()) {
                LOGGER.finest("Destroying cluster session: " + session.getId() + " => Ignore-timeout: true");
            }
            clusterMap.delete(session.getId());
            invalidated = true;
        } else {
            Boolean destroyed = (Boolean) clusterMap.executeOnKey(session.getId(), new DestroySessionEntryProcessor());

            invalidated = (destroyed != null && destroyed);
        }

        if (invalidated) {
            // If the session was invalidated, either explicitly or because the final reference to it was
            // destroyed, invalidate all of the attributes that were attached to it
            clusterMap.executeOnEntries(new InvalidateSessionAttributesEntryProcessor(session.getId()));
        }
    }

    private IMap<String, Object> getClusterMap() {
        return hazelcastInstance.getMap(clusterMapName);
    }

    private HazelcastHttpSession getSessionWithId(final String sessionId) {
        HazelcastHttpSession session = sessions.get(sessionId);
        if (session != null && !session.isValid()) {
            destroySession(session, true);
            session = null;
        }
        return session;
    }

    private void addSessionCookie(final RequestWrapper req, final String sessionId) {
        final Cookie sessionCookie = new Cookie(sessionCookieName, sessionId);
        String path = req.getContextPath();
        if ("".equals(path)) {
            path = "/";
        }
        sessionCookie.setPath(path);
        sessionCookie.setMaxAge(-1);
        if (sessionCookieDomain != null) {
            sessionCookie.setDomain(sessionCookieDomain);
        }
        if (sessionCookieHttpOnly) {
            try {
                sessionCookie.setHttpOnly(true);
            } catch (NoSuchMethodError e) {
                LOGGER.info("HttpOnly cookies require a Servlet 3.0+ container. Add the following to the "
                        + getClass().getName() + " mapping in web.xml to disable HttpOnly cookies:\n"
                        + "<init-param>\n"
                        + "    <param-name>cookie-http-only</param-name>\n"
                        + "    <param-value>false</param-value>\n"
                        + "</init-param>");
            }
        }
        sessionCookie.setSecure(sessionCookieSecure);
        req.res.addCookie(sessionCookie);
    }

    private String getSessionCookie(final RequestWrapper req) {
        final Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (final Cookie cookie : cookies) {
                final String name = cookie.getName();
                final String value = cookie.getValue();
                if (name.equalsIgnoreCase(sessionCookieName)) {
                    return value;
                }
            }
        }
        return null;
    }

    public final void doFilter(ServletRequest req, ServletResponse res, final FilterChain chain)
            throws IOException, ServletException {
        if (!(req instanceof HttpServletRequest)) {
            chain.doFilter(req, res);
        } else {
            if (req instanceof RequestWrapper) {
                LOGGER.finest("Request is instance of RequestWrapper! Continue...");
                chain.doFilter(req, res);
                return;
            }
            HttpServletRequest httpReq = (HttpServletRequest) req;
            RequestWrapper existingReq = (RequestWrapper) req.getAttribute(HAZELCAST_REQUEST);
            final ResponseWrapper resWrapper = new ResponseWrapper((HttpServletResponse) res);
            final RequestWrapper reqWrapper = new RequestWrapper(httpReq, resWrapper);
            if (existingReq != null) {
                reqWrapper.setHazelcastSession(existingReq.hazelcastSession, existingReq.requestedSessionId);
            }
            chain.doFilter(reqWrapper, resWrapper);
            if (existingReq != null) {
                return;
            }
            HazelcastHttpSession session = reqWrapper.getSession(false);
            if (session != null && session.isValid()) {
                if (LOGGER.isFinestEnabled()) {
                    LOGGER.finest("UPDATING SESSION " + session.getId());
                }
                session.sessionDeferredWrite();
            }
        }
    }

    public final void destroy() {
        sessions.clear();
        originalSessions.clear();
        shutdownInstance();
    }

    protected HazelcastInstance getInstance(Properties properties) throws ServletException {
        return HazelcastInstanceLoader.createInstance(filterConfig, properties);
    }

    protected void shutdownInstance() {
        if (shutdownOnDestroy && hazelcastInstance != null) {
            hazelcastInstance.getLifecycleService().shutdown();
        }
    }

    private String getParam(String name) {
        if (properties != null && properties.containsKey(name)) {
            return properties.getProperty(name);
        } else {
            return filterConfig.getInitParameter(name);
        }
    }

    protected static class ResponseWrapper extends HttpServletResponseWrapper {

        public ResponseWrapper(final HttpServletResponse original) {
            super(original);
        }
    }

    protected static class LocalCacheEntry {
        volatile boolean dirty;
        volatile boolean reload;
        boolean removed;
        private Object value;
    }

    protected class RequestWrapper extends HttpServletRequestWrapper {
        final ResponseWrapper res;
        HazelcastHttpSession hazelcastSession;
        String requestedSessionId;

        public RequestWrapper(final HttpServletRequest req,
                              final ResponseWrapper res) {
            super(req);
            this.res = res;
            req.setAttribute(HAZELCAST_REQUEST, this);
        }

        public void setHazelcastSession(HazelcastHttpSession hazelcastSession, String requestedSessionId) {
            this.hazelcastSession = hazelcastSession;
            this.requestedSessionId = requestedSessionId;
        }

        HttpSession getOriginalSession(boolean create) {
            return super.getSession(create);
        }

        @Override
        public RequestDispatcher getRequestDispatcher(final String path) {
            final ServletRequest original = getRequest();
            return new RequestDispatcher() {
                public void forward(ServletRequest servletRequest, ServletResponse servletResponse)
                        throws ServletException, IOException {
                    original.getRequestDispatcher(path).forward(servletRequest, servletResponse);
                }

                public void include(ServletRequest servletRequest, ServletResponse servletResponse)
                        throws ServletException, IOException {
                    original.getRequestDispatcher(path).include(servletRequest, servletResponse);
                }
            };
        }

        public HazelcastHttpSession fetchHazelcastSession(boolean create) {
            if (requestedSessionId == null) {
                requestedSessionId = getSessionCookie(this);
                if (requestedSessionId == null) {
                    requestedSessionId = getParameter(HAZELCAST_SESSION_COOKIE_NAME);
                }
            }

            if (requestedSessionId != null) {
                hazelcastSession = getSessionWithId(requestedSessionId);
                if (hazelcastSession == null) {
                    final Boolean existing = (Boolean) getClusterMap()
                            .executeOnKey(requestedSessionId, new ReferenceSessionEntryProcessor());
                    boolean canOrMustCreate = create || !RequestWrapper.this.res.isCommitted()
                            || getOriginalSession(false) != null;
                    if (existing != null && existing && canOrMustCreate) {
                        // we already have the session in the cluster, so "copy" it to this node
                        hazelcastSession = createNewSession(RequestWrapper.this, requestedSessionId);
                    }
                }
            }

            return hazelcastSession;
        }

        @Override
        public HttpSession getSession() {
            return getSession(true);
        }

        @Override
        public HazelcastHttpSession getSession(final boolean create) {
            if (hazelcastSession != null && !hazelcastSession.isValid()) {
                LOGGER.finest("Session is invalid!");
                destroySession(hazelcastSession, true);
                hazelcastSession = null;
            } else if (hazelcastSession != null) {
                return hazelcastSession;
            }

            HttpSession originalSession = getOriginalSession(false);
            if (originalSession != null) {
                String hazelcastSessionId = originalSessions.get(originalSession.getId());
                if (hazelcastSessionId != null) {
                    hazelcastSession = sessions.get(hazelcastSessionId);
                    return hazelcastSession;
                }
                originalSessions.remove(originalSession.getId());
                originalSession.invalidate();
            }

            hazelcastSession = fetchHazelcastSession(create);
            if (hazelcastSession == null && create) {
                hazelcastSession = createNewSession(RequestWrapper.this, null);
            }
            if (deferredWrite) {
                prepareReloadingSession(hazelcastSession);
            }
            return hazelcastSession;
        }
    } // END of RequestWrapper

    protected class HazelcastHttpSession implements HttpSession {

        volatile boolean valid = true;
        final String id;
        final HttpSession originalSession;
        private final Map<String, LocalCacheEntry> localCache;
        private final boolean deferredWrite;
        // only true if session is created first time in the cluster
        private volatile boolean clusterWideNew;

        public HazelcastHttpSession(final String sessionId, final HttpSession originalSession,
                                    final boolean deferredWrite) {
            this.id = sessionId;
            this.originalSession = originalSession;
            this.deferredWrite = deferredWrite;
            this.localCache = deferredWrite ? buildLocalCache() : null;
        }

        public HttpSession getOriginalSession() {
            return originalSession;
        }

        public String getOriginalSessionId() {
            return originalSession != null ? originalSession.getId() : null;
        }

        public Object getAttribute(final String name) {
            IMap<String, Object> clusterMap = getClusterMap();
            if (deferredWrite) {
                LocalCacheEntry cacheEntry = localCache.get(name);
                if (cacheEntry == null || (cacheEntry.reload && !cacheEntry.dirty)) {
                    Object value = clusterMap.get(buildAttributeName(name));
                    if (value == null) {
                        cacheEntry = NULL_ENTRY;
                    } else {
                        cacheEntry = new LocalCacheEntry();
                        cacheEntry.value = value;
                        cacheEntry.reload = false;
                    }
                    localCache.put(name, cacheEntry);
                }
                return cacheEntry != NULL_ENTRY ? cacheEntry.value : null;
            }
            return clusterMap.get(buildAttributeName(name));
        }

        public Enumeration<String> getAttributeNames() {
            final Set<String> keys = selectKeys();
            return new Enumeration<String>() {
                private final String[] elements = keys.toArray(new String[keys.size()]);
                private int index;

                @Override
                public boolean hasMoreElements() {
                    return index < elements.length;
                }

                @Override
                public String nextElement() {
                    return elements[index++];
                }
            };
        }

        public String getId() {
            return id;
        }

        public ServletContext getServletContext() {
            return servletContext;
        }

        @Deprecated
        @SuppressWarnings("deprecation")
        public HttpSessionContext getSessionContext() {
            return originalSession.getSessionContext();
        }

        public Object getValue(final String name) {
            return getAttribute(name);
        }

        public String[] getValueNames() {
            final Set<String> keys = selectKeys();
            return keys.toArray(new String[keys.size()]);
        }

        public void invalidate() {
            originalSession.invalidate();
            destroySession(this, true);
        }

        public boolean isNew() {
            return originalSession.isNew() && clusterWideNew;
        }

        public void putValue(final String name, final Object value) {
            setAttribute(name, value);
        }

        public void removeAttribute(final String name) {
            if (deferredWrite) {
                LocalCacheEntry entry = localCache.get(name);
                if (entry != null && entry != NULL_ENTRY) {
                    entry.value = null;
                    entry.removed = true;
                    // dirty needs to be set as last value for memory visibility reasons!
                    entry.dirty = true;
                }
            } else {
                getClusterMap().delete(buildAttributeName(name));
            }
        }

        public void setAttribute(final String name, final Object value) {
            if (name == null) {
                throw new NullPointerException("name must not be null");
            }
            if (value == null) {
                removeAttribute(name);
                return;
            }
            if (deferredWrite) {
                LocalCacheEntry entry = localCache.get(name);
                if (entry == null || entry == NULL_ENTRY) {
                    entry = new LocalCacheEntry();
                    localCache.put(name, entry);
                }
                entry.value = value;
                entry.dirty = true;
            } else {
                getClusterMap().set(buildAttributeName(name), value);
            }
        }

        public void removeValue(final String name) {
            removeAttribute(name);
        }

        /**
         * @return {@code true} if {@link #deferredWrite} is enabled <i>and</i> at least one entry in the local
         * cache is dirty; otherwise, {@code false}
         */
        public boolean sessionChanged() {
            if (!deferredWrite) {
                return false;
            }
            for (Entry<String, LocalCacheEntry> entry : localCache.entrySet()) {
                if (entry.getValue().dirty) {
                    return true;
                }
            }
            return false;
        }

        public long getCreationTime() {
            return originalSession.getCreationTime();
        }

        public long getLastAccessedTime() {
            return originalSession.getLastAccessedTime();
        }

        public int getMaxInactiveInterval() {
            return originalSession.getMaxInactiveInterval();
        }

        public void setMaxInactiveInterval(int maxInactiveSeconds) {
            originalSession.setMaxInactiveInterval(maxInactiveSeconds);
        }

        void destroy() {
            valid = false;
        }

        public boolean isValid() {
            return valid;
        }

        private String buildAttributeName(String name) {
            return id + HAZELCAST_SESSION_ATTRIBUTE_SEPARATOR + name;
        }

        private Map<String, LocalCacheEntry> buildLocalCache() {
            Set<Entry<String, Object>> entrySet = getClusterMap().entrySet(new SessionAttributePredicate(id));

            Map<String, LocalCacheEntry> cache = new ConcurrentHashMap<String, LocalCacheEntry>();
            for (Entry<String, Object> entry : entrySet) {
                String attributeKey = extractAttributeKey(entry.getKey());
                LocalCacheEntry cacheEntry = cache.get(attributeKey);
                if (cacheEntry == null) {
                    cacheEntry = new LocalCacheEntry();
                    cache.put(attributeKey, cacheEntry);
                }
                if (LOGGER.isFinestEnabled()) {
                    LOGGER.finest("Storing " + attributeKey + " on session " + id);
                }
                cacheEntry.value = entry.getValue();
                cacheEntry.dirty = false;
            }

            return cache;
        }

        private void sessionDeferredWrite() {
            if (sessionChanged()) {
                IMap<String, Object> clusterMap = getClusterMap();

                Iterator<Entry<String, LocalCacheEntry>> iterator = localCache.entrySet().iterator();
                while (iterator.hasNext()) {
                    Entry<String, LocalCacheEntry> entry = iterator.next();
                    if (entry.getValue().dirty) {
                        LocalCacheEntry cacheEntry = entry.getValue();
                        if (cacheEntry.removed) {
                            clusterMap.delete(buildAttributeName(entry.getKey()));
                            iterator.remove();
                        } else {
                            clusterMap.set(buildAttributeName(entry.getKey()), cacheEntry.value);
                            cacheEntry.dirty = false;
                        }
                    }
                }
            }
        }

        private Set<String> selectKeys() {
            Set<String> keys = new HashSet<String>();
            if (!deferredWrite) {
                for (String qualifiedAttributeKey : getClusterMap().keySet(new SessionAttributePredicate(id))) {
                    keys.add(extractAttributeKey(qualifiedAttributeKey));
                }
            } else {
                for (Entry<String, LocalCacheEntry> entry : localCache.entrySet()) {
                    if (!entry.getValue().removed && entry.getValue() != NULL_ENTRY) {
                        keys.add(entry.getKey());
                    }
                }
            }
            return keys;
        }

        public void setClusterWideNew(boolean clusterWideNew) {
            this.clusterWideNew = clusterWideNew;
        }
    } // END of HazelSession
} // END of WebFilter
TOP

Related Classes of com.hazelcast.web.WebFilter$HazelcastHttpSession

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.