/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ignite.cache.websession; import java.io.IOException; import java.util.Collection; import java.util.Map; import javax.cache.CacheException; import javax.cache.expiry.Duration; import javax.cache.expiry.ExpiryPolicy; import javax.cache.expiry.ModifiedExpiryPolicy; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpSession; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteClientDisconnectedException; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.IgniteTransactions; import org.apache.ignite.cluster.ClusterTopologyException; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.internal.util.typedef.C1; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.websession.WebSessionAttributeProcessor; import org.apache.ignite.internal.websession.WebSessionEntity; import org.apache.ignite.lang.IgniteClosure; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.marshaller.Marshaller; import org.apache.ignite.startup.servlet.ServletContextListenerStartup; import org.apache.ignite.transactions.Transaction; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC; import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; import static org.apache.ignite.cache.CacheMode.LOCAL; import static org.apache.ignite.cache.CacheMode.PARTITIONED; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_ASYNC; import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC; import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ; /** * Filter for web sessions caching. * <p> * This is a request filter, that you need to specify in your {@code web.xml} along * with {@link ServletContextListenerStartup} to enable web sessions caching: * <pre name="code" class="xml"> * <listener> * <listener-class>org.apache.ignite.startup.servlet.ServletContextListenerStartup</listener-class> * </listener> * * <filter> * <filter-name>WebSessionFilter</filter-name> * <filter-class>org.apache.ignite.cache.websession.WebSessionFilter</filter-class> * </filter> * * <!-- You can also specify a custom URL pattern. --> * <filter-mapping> * <filter-name>IgniteWebSessionsFilter</filter-name> * <url-pattern>/*</url-pattern> * </filter-mapping> * </pre> * It is also possible to specify a servlet name in a filter mapping, and a servlet URL pattern will * be used in this case: * <pre name="code" class="xml"> * <filter> * <filter-name>WebSessionFilter</filter-name> * <filter-class>org.apache.ignite.cache.websession.WebSessionFilter</filter-class> * </filter> * * <filter-mapping> * <filter-name>WebSessionFilter</filter-name> * <servlet-name>YourServletName</servlet-name> * </filter-mapping> * </pre> * The filter has the following optional configuration parameters: * <table class="doctable"> * <tr> * <th>Name</th> * <th>Description</th> * <th>Default</th> * </tr> * <tr> * <td>IgniteWebSessionsGridName</td> * <td>Name of the grid that contains cache for web session storage.</td> * <td>{@code null} (default grid)</td> * </tr> * <tr> * <td>IgniteWebSessionsCacheName</td> * <td>Name of the cache for web session storage.</td> * <td>{@code null} (default cache)</td> * </tr> * <tr> * <td>IgniteWebSessionsMaximumRetriesOnFail</td> * <td> * Valid for {@code ATOMIC} caches only. Maximum number of retries for session updates in case * node leaves topology and update fails. If retry is enabled, * some updates can be applied more than once, otherwise some * updates can be lost. * <p> * To disable retries, set this parameter to {@code 0}. * </td> * <td>{@code 3}</td> * </tr> * <tr> * <td>IgniteWebSessionsRetriesTimeout</td> * <td> * Retry timeout. Related to IgniteWebSessionsMaximumRetriesOnFail param. * <p> * Further attempts will be cancelled in case timeout was exceeded. * </td> * <td>{@code 10000} (10 seconds)</td> * </tr> * </table> * These parameters are taken from either filter init parameter list or * servlet context parameters. You can specify filter init parameters as follows: * <pre name="code" class="xml"> * <filter> * <filter-name>WebSessionFilter</filter-name> * <filter-class>org.apache.ignite.cache.websession.WebSessionFilter</filter-class> * <init-param> * <param-name>IgniteWebSessionsGridName</param-name> * <param-value>WebGrid</param-value> * </init-param> * <init-param> * <param-name>IgniteWebSessionsCacheName</param-name> * <param-value>WebCache</param-value> * </init-param> * * <!-- Valid for ATOMIC caches only. --> * <init-param> * <param-name>IgniteWebSessionsMaximumRetriesOnFail</param-name> * <param-value>10</param-value> * </init-param> * </filter> * </pre> * <b>Note:</b> filter init parameter has a priority over servlet context * parameter; if you specify both, the servlet context parameter will be ignored. * <h1 class="header">Web sessions caching and concurrent requests</h1> * If your web application can accept concurrent request for one session, * consider using {@link org.apache.ignite.cache.CacheAtomicityMode#TRANSACTIONAL} cache * instead of {@link org.apache.ignite.cache.CacheAtomicityMode#ATOMIC}. In this case each request * be processed inside pessimistic transaction which will guarantee that all * updates will be applied in correct order. This is important, for example, * if you get some attribute from the session, update its value and set new * value back to the session. In case of {@link org.apache.ignite.cache.CacheAtomicityMode#ATOMIC} * cache concurrent requests can get equal value, but {@link org.apache.ignite.cache.CacheAtomicityMode#TRANSACTIONAL} * cache will always process such updates one after another. */ public class WebSessionFilter implements Filter { /** Web sessions caching grid name parameter name. */ public static final String WEB_SES_NAME_PARAM = "IgniteWebSessionsGridName"; /** Web sessions caching cache name parameter name. */ public static final String WEB_SES_CACHE_NAME_PARAM = "IgniteWebSessionsCacheName"; /** Web sessions caching retry on fail parameter name (valid for ATOMIC cache only). */ public static final String WEB_SES_MAX_RETRIES_ON_FAIL_NAME_PARAM = "IgniteWebSessionsMaximumRetriesOnFail"; /** Web sessions caching retry on fail timeout parameter name. */ public static final String WEB_SES_RETRIES_TIMEOUT_NAME_PARAM = "IgniteWebSessionsRetriesTimeout"; /** */ public static final String WEB_SES_KEEP_BINARY_PARAM = "IgniteWebSessionsKeepBinary"; /** Default retry on fail flag value. */ public static final int DFLT_MAX_RETRIES_ON_FAIL = 3; /** Default retry on fail timeout flag value. */ public static final int DFLT_RETRIES_ON_FAIL_TIMEOUT = 10000; /** Default keep binary flag. */ public static final boolean DFLT_KEEP_BINARY_FLAG = true; /** Cache. */ private IgniteCache<String, WebSession> cache; /** Binary cache */ private IgniteCache<String, WebSessionEntity> binaryCache; /** Transactions. */ private IgniteTransactions txs; /** Logger. */ private IgniteLogger log; /** Servlet context. */ private ServletContext ctx; /** Session ID transformer. */ private IgniteClosure<String, String> sesIdTransformer; /** Transactions enabled flag. */ private boolean txEnabled; /** Node. */ private Ignite webSesIgnite; /** Cache name. */ private String cacheName; /** */ private int retries; /** */ private int retriesTimeout; /** */ private boolean keepBinary = DFLT_KEEP_BINARY_FLAG; /** */ private Marshaller marshaller; /** {@inheritDoc} */ @Override public void init(FilterConfig cfg) throws ServletException { ctx = cfg.getServletContext(); String igniteInstanceName = U.firstNotNull( cfg.getInitParameter(WEB_SES_NAME_PARAM), ctx.getInitParameter(WEB_SES_NAME_PARAM)); cacheName = U.firstNotNull( cfg.getInitParameter(WEB_SES_CACHE_NAME_PARAM), ctx.getInitParameter(WEB_SES_CACHE_NAME_PARAM)); String retriesStr = U.firstNotNull( cfg.getInitParameter(WEB_SES_MAX_RETRIES_ON_FAIL_NAME_PARAM), ctx.getInitParameter(WEB_SES_MAX_RETRIES_ON_FAIL_NAME_PARAM)); try { retries = retriesStr != null ? Integer.parseInt(retriesStr) : DFLT_MAX_RETRIES_ON_FAIL; } catch (NumberFormatException e) { throw new IgniteException("Maximum number of retries parameter is invalid: " + retriesStr, e); } String retriesTimeoutStr = U.firstNotNull( cfg.getInitParameter(WEB_SES_RETRIES_TIMEOUT_NAME_PARAM), ctx.getInitParameter(WEB_SES_RETRIES_TIMEOUT_NAME_PARAM)); try { retriesTimeout = retriesTimeoutStr != null ? Integer.parseInt(retriesTimeoutStr) : DFLT_RETRIES_ON_FAIL_TIMEOUT; } catch (NumberFormatException e) { throw new IgniteException("Retries timeout parameter is invalid: " + retriesTimeoutStr, e); } final String binParam = cfg.getInitParameter(WEB_SES_KEEP_BINARY_PARAM); if (!F.isEmpty(binParam)) keepBinary = Boolean.parseBoolean(binParam); webSesIgnite = G.ignite(igniteInstanceName); if (webSesIgnite == null) throw new IgniteException("Ignite instance for web sessions caching is not started (is it configured?): " + igniteInstanceName); txs = webSesIgnite.transactions(); log = webSesIgnite.log(); marshaller = webSesIgnite.configuration().getMarshaller(); initCache(); String srvInfo = ctx.getServerInfo(); // Special case for WebLogic, which appends timestamps to session // IDs upon session creation (the created session ID looks like: // pdpTSTcCcG6CVM8BTZWzxjTB1lh3w7zFbYVvwBb4bJGjrBx3TMPl!-508312620!1385045122601). if (srvInfo != null && srvInfo.contains("WebLogic")) { sesIdTransformer = new C1<String, String>() { @Override public String apply(String s) { // Find first exclamation mark. int idx = s.indexOf('!'); // Return original string if not found. if (idx < 0 || idx == s.length() - 1) return s; // Find second exclamation mark. idx = s.indexOf('!', idx + 1); // Return original string if not found. if (idx < 0) return s; // Return the session ID without timestamp. return s.substring(0, idx); } }; } if (log.isInfoEnabled()) log.info("Started web sessions caching [igniteInstanceName=" + igniteInstanceName + ", cacheName=" + cacheName + ", maxRetriesOnFail=" + retries + ']'); } /** * Init cache. */ @SuppressWarnings("unchecked") void initCache() { cache = webSesIgnite.cache(cacheName); binaryCache = webSesIgnite.cache(cacheName); if (cache == null) throw new IgniteException("Cache for web sessions is not started (is it configured?): " + cacheName); CacheConfiguration cacheCfg = cache.getConfiguration(CacheConfiguration.class); if (cacheCfg.getWriteSynchronizationMode() == FULL_ASYNC) throw new IgniteException("Cache for web sessions cannot be in FULL_ASYNC mode: " + cacheName); if (!cacheCfg.isEagerTtl()) throw new IgniteException("Cache for web sessions cannot operate with lazy TTL. " + "Consider setting eagerTtl to true for cache: " + cacheName); if (cacheCfg.getCacheMode() == LOCAL) U.quietAndWarn(webSesIgnite.log(), "Using LOCAL cache for web sessions caching " + "(this is only OK in test mode): " + cacheName); if (cacheCfg.getCacheMode() == PARTITIONED && cacheCfg.getAtomicityMode() != ATOMIC) U.quietAndWarn(webSesIgnite.log(), "Using " + cacheCfg.getAtomicityMode() + " atomicity for web sessions " + "caching (switch to ATOMIC mode for better performance)"); txEnabled = cacheCfg.getAtomicityMode() == TRANSACTIONAL; } /** {@inheritDoc} */ @Override public void destroy() { // No-op. } /** {@inheritDoc} */ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { assert ctx != null; if (req instanceof HttpServletRequest) { HttpServletRequest httpReq = (HttpServletRequest)req; String sesId = null; try { if (txEnabled) { try (Transaction tx = txs.txStart(PESSIMISTIC, REPEATABLE_READ)) { sesId = doFilterDispatch(httpReq, res, chain); tx.commit(); } } else sesId = doFilterDispatch(httpReq, res, chain); } catch (Exception e) { U.error(log, "Failed to update web session: " + sesId, e); } } else chain.doFilter(req, res); } /** * Use {@link WebSession} or {@link WebSessionV2} according to {@link #keepBinary} flag. * * @param httpReq Request. * @param res Response. * @param chain Filter chain. * @return Session ID. * @throws IOException * @throws ServletException * @throws CacheException */ private String doFilterDispatch(HttpServletRequest httpReq, ServletResponse res, FilterChain chain) throws IOException, ServletException, CacheException { if (keepBinary) return doFilterV2(httpReq, res, chain); return doFilterV1(httpReq, res, chain); } /** * @param httpReq Request. * @param res Response. * @param chain Filter chain. * @return Session ID. * @throws IOException In case of I/O error. * @throws ServletException In case of servlet error. * @throws CacheException In case of other error. */ private String doFilterV1(HttpServletRequest httpReq, ServletResponse res, FilterChain chain) throws IOException, ServletException, CacheException { WebSession cached = null; String sesId = httpReq.getRequestedSessionId(); if (sesId != null) { sesId = transformSessionId(sesId); for (int i = 0; i < retries; i++) { try { cached = cache.get(sesId); break; } catch (CacheException | IgniteException | IllegalStateException e) { handleLoadSessionException(sesId, i, e); } } if (cached != null) { if (log.isDebugEnabled()) log.debug("Using cached session for ID: " + sesId); if (cached.isNew()) cached = new WebSession(cached.getId(), cached, false); } else { if (log.isDebugEnabled()) log.debug("Cached session was invalidated and doesn't exist: " + sesId); HttpSession ses = httpReq.getSession(false); if (ses != null) { try { ses.invalidate(); } catch (IllegalStateException ignore) { // Session was already invalidated. } } cached = createSession(httpReq); } } else cached = createSession(httpReq); assert cached != null; sesId = cached.getId(); cached.servletContext(ctx); cached.filter(this); cached.resetUpdates(); cached.genSes(httpReq.getSession(false)); httpReq = new RequestWrapper(httpReq, cached); chain.doFilter(httpReq, res); HttpSession ses = httpReq.getSession(false); if (ses != null && ses instanceof WebSession) { Collection<T2<String, Object>> updates = ((WebSession) ses).updates(); if (updates != null) updateAttributes(transformSessionId(sesId), updates, ses.getMaxInactiveInterval()); } return sesId; } /** * @param httpReq Request. * @param res Response. * @param chain Filter chain. * @return Session ID. * @throws IOException In case of I/O error. * @throws ServletException In case oif servlet error. * @throws CacheException In case of other error. */ private String doFilterV2(HttpServletRequest httpReq, ServletResponse res, FilterChain chain) throws IOException, ServletException, CacheException { WebSessionV2 cached = null; String sesId = httpReq.getRequestedSessionId(); if (sesId != null) { sesId = transformSessionId(sesId); // Load from cache. for (int i = 0; i < retries; i++) { try { final WebSessionEntity entity = binaryCache.get(sesId); if (entity != null) cached = new WebSessionV2(sesId, httpReq.getSession(false), false, ctx, entity, marshaller); break; } catch (CacheException | IgniteException | IllegalStateException e) { handleLoadSessionException(sesId, i, e); } } if (cached != null) { if (log.isDebugEnabled()) log.debug("Using cached session for ID: " + sesId); } // If not found - invalidate session and create new one. // Invalidate, because session might be removed from cache // according to expiry policy. else { if (log.isDebugEnabled()) log.debug("Cached session was invalidated and doesn't exist: " + sesId); final HttpSession ses = httpReq.getSession(false); if (ses != null) { try { ses.invalidate(); } catch (IllegalStateException ignore) { // Session was already invalidated. } } cached = createSessionV2(httpReq); } } // No session was requested by the client, create new one and put in the request. else cached = createSessionV2(httpReq); assert cached != null; sesId = cached.getId(); httpReq = new RequestWrapperV2(httpReq, cached); chain.doFilter(httpReq, res); WebSessionV2 cachedNew = (WebSessionV2)httpReq.getSession(false); if (cachedNew != null && cachedNew.isValid()) updateAttributesV2(cachedNew.getId(), cachedNew); return sesId; } /** * Log and process exception happened on loading session from cache. * * @param sesId Session ID. * @param tryCnt Try count. * @param e Caught exception. */ private void handleLoadSessionException(final String sesId, final int tryCnt, final RuntimeException e) { if (log.isDebugEnabled()) log.debug(e.getMessage()); if (tryCnt == retries - 1) throw new IgniteException("Failed to handle request [session= " + sesId + "]", e); else { if (log.isDebugEnabled()) log.debug("Failed to handle request (will retry): " + sesId); handleCacheOperationException(e); } } /** * Transform session ID if ID transformer present. * * @param sesId Session ID to transform. * @return Transformed session ID or the same if no session transformer available. */ private String transformSessionId(final String sesId) { if (sesIdTransformer != null) return sesIdTransformer.apply(sesId); return sesId; } /** * Creates a new session from http request. * * @param httpReq Request. * @return New session. */ @SuppressWarnings("unchecked") private WebSession createSession(HttpServletRequest httpReq) { HttpSession ses = httpReq.getSession(true); String sesId = transformSessionId(ses.getId()); return createSession(ses, sesId); } /** * Creates a new web session with the specified id. * * @param ses Base session. * @param sesId Session id. * @return New session. */ @SuppressWarnings("unchecked") private WebSession createSession(HttpSession ses, String sesId) { WebSession cached = new WebSession(sesId, ses, true); cached.genSes(ses); if (log.isDebugEnabled()) log.debug("Session created: " + sesId); for (int i = 0; i < retries; i++) { try { final IgniteCache<String, WebSession> cache0 = cacheWithExpiryPolicy(cached.getMaxInactiveInterval(), cache); final WebSession old = cache0.getAndPutIfAbsent(sesId, cached); if (old != null) { cached = old; if (cached.isNew()) cached = new WebSession(cached.getId(), cached, false); } break; } catch (CacheException | IgniteException | IllegalStateException e) { handleCreateSessionException(sesId, i, e); } } return cached; } /** * Log error and delegate exception processing to {@link #handleCacheOperationException(Exception)} * * @param sesId Session ID. * @param tryCnt Try count. * @param e Exception to process. */ private void handleCreateSessionException(final String sesId, final int tryCnt, final RuntimeException e) { if (log.isDebugEnabled()) log.debug(e.getMessage()); if (tryCnt == retries - 1) throw new IgniteException("Failed to save session: " + sesId, e); else { if (log.isDebugEnabled()) log.debug("Failed to save session (will retry): " + sesId); handleCacheOperationException(e); } } /** * Creates a new web session with the specified id. * * @param ses Base session. * @param sesId Session id. * @return New session. */ private WebSessionV2 createSessionV2(final HttpSession ses, final String sesId) throws IOException { assert ses != null; assert sesId != null; WebSessionV2 cached = new WebSessionV2(sesId, ses, true, ctx, null, marshaller); final WebSessionEntity marshaledEntity = cached.marshalAttributes(); for (int i = 0; i < retries; i++) { try { final IgniteCache<String, WebSessionEntity> cache0 = cacheWithExpiryPolicy( cached.getMaxInactiveInterval(), binaryCache); final WebSessionEntity old = cache0.getAndPutIfAbsent(sesId, marshaledEntity); if (old != null) cached = new WebSessionV2(sesId, ses, false, ctx, old, marshaller); else cached = new WebSessionV2(sesId, ses, false, ctx, marshaledEntity, marshaller); break; } catch (CacheException | IgniteException | IllegalStateException e) { handleCreateSessionException(sesId, i, e); } } return cached; } /** * @param httpReq HTTP request. * @return Cached session. */ private WebSessionV2 createSessionV2(HttpServletRequest httpReq) throws IOException { final HttpSession ses = httpReq.getSession(true); final String sesId = transformSessionId(ses.getId()); if (log.isDebugEnabled()) log.debug("Session created: " + sesId); return createSessionV2(ses, sesId); } /** * @param maxInactiveInteval Interval to use in expiry policy. * @param cache Cache. * @param <T> Cached object type. * @return Cache with expiry policy if {@code maxInactiveInteval} greater than zero. */ private <T> IgniteCache<String, T> cacheWithExpiryPolicy(final int maxInactiveInteval, final IgniteCache<String, T> cache) { if (maxInactiveInteval > 0) { long ttl = maxInactiveInteval * 1000; ExpiryPolicy plc = new ModifiedExpiryPolicy(new Duration(MILLISECONDS, ttl)); return cache.withExpiryPolicy(plc); } return cache; } /** * @param sesId Session ID. */ public void destroySession(String sesId) { assert sesId != null; for (int i = 0; i < retries; i++) { try { if (cache.remove(sesId) && log.isDebugEnabled()) log.debug("Session destroyed: " + sesId); } catch (CacheException | IgniteException | IllegalStateException e) { if (i == retries - 1) { U.warn(log, "Failed to remove session [sesId=" + sesId + ", retries=" + retries + ']'); } else { U.warn(log, "Failed to remove session (will retry): " + sesId); handleCacheOperationException(e); } } } } /** * @param sesId Session ID. * @param updates Updates list. * @param maxInactiveInterval Max session inactive interval. */ @SuppressWarnings("unchecked") public void updateAttributes(String sesId, Collection<T2<String, Object>> updates, int maxInactiveInterval) { assert sesId != null; assert updates != null; if (log.isDebugEnabled()) log.debug("Session attributes updated [id=" + sesId + ", updates=" + updates + ']'); try { for (int i = 0; i < retries; i++) { try { final IgniteCache<String, WebSession> cache0 = cacheWithExpiryPolicy(maxInactiveInterval, cache); cache0.invoke(sesId, WebSessionListener.newAttributeProcessor(updates)); break; } catch (CacheException | IgniteException | IllegalStateException e) { handleAttributeUpdateException(sesId, i, e); } } } catch (Exception e) { U.error(log, "Failed to update session attributes [id=" + sesId + ']', e); } } /** * @param sesId Session ID. * @param ses Web session. */ public void updateAttributesV2(final String sesId, final WebSessionV2 ses) throws IOException { assert sesId != null; assert ses != null; final Map<String, byte[]> updatesMap = ses.binaryUpdatesMap(); if (log.isDebugEnabled()) log.debug("Session binary attributes updated [id=" + sesId + ", updates=" + updatesMap.keySet() + ']'); try { for (int i = 0; i < retries; i++) { try { final IgniteCache<String, WebSessionEntity> cache0 = cacheWithExpiryPolicy(ses.getMaxInactiveInterval(), binaryCache); cache0.invoke(sesId, new WebSessionAttributeProcessor(updatesMap.isEmpty() ? null : updatesMap, ses.getLastAccessedTime(), ses.getMaxInactiveInterval(), ses.isMaxInactiveIntervalChanged())); break; } catch (CacheException | IgniteException | IllegalStateException e) { handleAttributeUpdateException(sesId, i, e); } } } catch (Exception e) { U.error(log, "Failed to update session V2 attributes [id=" + sesId + ']', e); } } /** * Log error and delegate processing to {@link #handleCacheOperationException(Exception)}. * * @param sesId Session ID. * @param tryCnt Try count. * @param e Exception to process. */ private void handleAttributeUpdateException(final String sesId, final int tryCnt, final RuntimeException e) { if (tryCnt == retries - 1) { U.error(log, "Failed to apply updates for session (maximum number of retries exceeded) [sesId=" + sesId + ", retries=" + retries + ']', e); } else { U.warn(log, "Failed to apply updates for session (will retry): " + sesId); handleCacheOperationException(e); } } /** * Handles cache operation exception. * @param e Exception */ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") void handleCacheOperationException(Exception e){ IgniteFuture<?> retryFut = null; if (e instanceof IllegalStateException) { initCache(); return; } else if (X.hasCause(e, IgniteClientDisconnectedException.class)) { IgniteClientDisconnectedException cause = X.cause(e, IgniteClientDisconnectedException.class); assert cause != null : e; retryFut = cause.reconnectFuture(); } else if (X.hasCause(e, ClusterTopologyException.class)) { ClusterTopologyException cause = X.cause(e, ClusterTopologyException.class); assert cause != null : e; retryFut = cause.retryReadyFuture(); } if (retryFut != null) { try { retryFut.get(retriesTimeout); } catch (IgniteException retryErr) { throw new IgniteException("Failed to wait for retry: " + retryErr); } } } /** {@inheritDoc} */ @Override public String toString() { return S.toString(WebSessionFilter.class, this); } /** * Request wrapper. */ private class RequestWrapper extends HttpServletRequestWrapper { /** Session. */ private volatile WebSession ses; /** * @param req Request. * @param ses Session. */ private RequestWrapper(HttpServletRequest req, WebSession ses) { super(req); assert ses != null; this.ses = ses; } /** {@inheritDoc} */ @Override public HttpSession getSession(boolean create) { if (!ses.isValid()) { if (create) { this.ses = createSession((HttpServletRequest)getRequest()); this.ses.servletContext(ctx); this.ses.filter(WebSessionFilter.this); this.ses.resetUpdates(); } else return null; } return ses; } /** {@inheritDoc} */ @Override public HttpSession getSession() { return getSession(true); } /** {@inheritDoc} */ @Override public String changeSessionId() { HttpServletRequest req = (HttpServletRequest)getRequest(); String newId = req.changeSessionId(); this.ses.setId(newId); this.ses = createSession(ses, newId); this.ses.servletContext(ctx); this.ses.filter(WebSessionFilter.this); this.ses.resetUpdates(); return newId; } /** {@inheritDoc} */ @Override public void login(String username, String password) throws ServletException{ HttpServletRequest req = (HttpServletRequest)getRequest(); req.login(username, password); String newId = req.getSession(false).getId(); this.ses.setId(newId); this.ses = createSession(ses, newId); this.ses.servletContext(ctx); this.ses.filter(WebSessionFilter.this); this.ses.resetUpdates(); } } /** * Request wrapper V2. */ private class RequestWrapperV2 extends HttpServletRequestWrapper { /** Session. */ private WebSessionV2 ses; /** * @param req Request. * @param ses Session. */ private RequestWrapperV2(HttpServletRequest req, WebSessionV2 ses) { super(req); assert ses != null; this.ses = ses; } /** {@inheritDoc} */ @Override public HttpSession getSession(boolean create) { if (!ses.isValid()) { binaryCache.remove(ses.id()); if (create) { try { ses = createSessionV2((HttpServletRequest) getRequest()); } catch (IOException e) { throw new IgniteException(e); } } else ses = null; } return ses; } /** {@inheritDoc} */ @Override public HttpSession getSession() { return getSession(true); } /** {@inheritDoc} */ @Override public String changeSessionId() { final HttpServletRequest req = (HttpServletRequest) getRequest(); final String newId = req.changeSessionId(); if (!F.eq(newId, ses.getId())) { try { ses = createSessionV2(ses, newId); } catch (IOException e) { throw new IgniteException(e); } } return newId; } /** {@inheritDoc} */ @Override public void login(String username, String password) throws ServletException{ final HttpServletRequest req = (HttpServletRequest)getRequest(); req.login(username, password); final String newId = req.getSession(false).getId(); if (!F.eq(newId, ses.getId())) { try { ses = createSessionV2(ses, newId); } catch (IOException e) { throw new IgniteException(e); } } } } }