ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From voze...@apache.org
Subject [22/38] ignite git commit: IGNITE-3087: WebSessions: User no longer required to have session classes and servlet/ignite-web in the classpath of server node.
Date Wed, 18 May 2016 10:58:28 GMT
IGNITE-3087: WebSessions: User no longer required to have session classes and servlet/ignite-web in the classpath of server node.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/ee1cd486
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/ee1cd486
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/ee1cd486

Branch: refs/heads/ignite-3165
Commit: ee1cd486fdb91603d7a69a1cf0166c5ed13dc383
Parents: 8731e46
Author: dkarachentsev <dkarachentsev@gridgain.com>
Authored: Thu May 12 11:44:20 2016 +0300
Committer: vozerov-gridgain <vozerov@gridgain.com>
Committed: Thu May 12 11:44:20 2016 +0300

----------------------------------------------------------------------
 .../WebSessionAttributeProcessor.java           | 135 ++++++
 .../internal/websession/WebSessionEntity.java   | 193 ++++++++
 .../ignite/cache/websession/WebSession.java     |  13 +-
 .../cache/websession/WebSessionFilter.java      | 447 ++++++++++++++++---
 .../cache/websession/WebSessionListener.java    | 117 +----
 .../ignite/cache/websession/WebSessionV2.java   | 397 ++++++++++++++++
 .../internal/websession/WebSessionSelfTest.java | 133 +++++-
 .../webapp2/META-INF/ignite-webapp-config.xml   | 288 ++++++++++++
 modules/web/src/test/webapp2/WEB-INF/web.xml    |  37 ++
 9 files changed, 1575 insertions(+), 185 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionAttributeProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionAttributeProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionAttributeProcessor.java
new file mode 100644
index 0000000..35b4d90
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionAttributeProcessor.java
@@ -0,0 +1,135 @@
+/*
+ * 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.internal.websession;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.internal.util.typedef.F;
+
+import javax.cache.processor.EntryProcessor;
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.MutableEntry;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Updates web session attributes according to {@link #updatesMap} and {@link #accessTime},
+ * {@link #maxInactiveInterval}.
+ */
+public class WebSessionAttributeProcessor implements EntryProcessor<String, WebSessionEntity, Void>,
+    Serializable, Binarylizable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Attribute updates. */
+    private Map<String, byte[]> updatesMap;
+
+    /** Access time update. */
+    private long accessTime;
+
+    /** Max inactive interval update. */
+    private int maxInactiveInterval;
+
+    /** Indicates whether apply or not max inactive interval update. */
+    private boolean maxIntervalChanged;
+
+    /**
+     * Empty constructor for serialization.
+     */
+    public WebSessionAttributeProcessor() {
+        // No-op.
+    }
+
+    /**
+     * Constructs attribute processor.
+     *
+     * @param updatesMap Updates that should be performed on entity attributes.
+     * @param accessTime Access time.
+     * @param maxInactiveInterval Max inactive interval.
+     * @param maxIntervalChanged {@code True} if max inactive interval should be updated.
+     */
+    public WebSessionAttributeProcessor(
+        final Map<String, byte[]> updatesMap, final long accessTime, final int maxInactiveInterval,
+        final boolean maxIntervalChanged) {
+        this.updatesMap = updatesMap;
+        this.accessTime = accessTime;
+        this.maxInactiveInterval = maxInactiveInterval;
+        this.maxIntervalChanged = maxIntervalChanged;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(final BinaryWriter writer) throws BinaryObjectException {
+        final BinaryRawWriter rawWriter = writer.rawWriter();
+
+        rawWriter.writeMap(updatesMap);
+        rawWriter.writeLong(accessTime);
+        rawWriter.writeBoolean(maxIntervalChanged);
+
+        if (maxIntervalChanged)
+            rawWriter.writeInt(maxInactiveInterval);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(final BinaryReader reader) throws BinaryObjectException {
+        final BinaryRawReader rawReader = reader.rawReader();
+
+        updatesMap = rawReader.readMap();
+        accessTime = rawReader.readLong();
+        maxIntervalChanged = rawReader.readBoolean();
+
+        if (maxIntervalChanged)
+            maxInactiveInterval = rawReader.readInt();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Void process(final MutableEntry<String, WebSessionEntity> entry,
+        final Object... arguments) throws EntryProcessorException {
+        final WebSessionEntity entity = entry.getValue();
+
+        final WebSessionEntity newEntity = new WebSessionEntity(entity);
+
+        if (newEntity.accessTime() < accessTime)
+            newEntity.accessTime(accessTime);
+
+        if (maxIntervalChanged)
+            newEntity.maxInactiveInterval(maxInactiveInterval);
+
+        if (!F.isEmpty(updatesMap)) {
+            for (final Map.Entry<String, byte[]> update : updatesMap.entrySet()) {
+                final String name = update.getKey();
+
+                assert name != null;
+
+                final byte[] val = update.getValue();
+
+                if (val != null)
+                    newEntity.putAttribute(name, val);
+                else
+                    newEntity.removeAttribute(name);
+            }
+        }
+
+        entry.setValue(newEntity);
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionEntity.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionEntity.java b/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionEntity.java
new file mode 100644
index 0000000..c504ee0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/websession/WebSessionEntity.java
@@ -0,0 +1,193 @@
+/*
+ * 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.internal.websession;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.internal.util.tostring.GridToStringExclude;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity
+ */
+public class WebSessionEntity implements Serializable, Binarylizable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Session ID. */
+    private String id;
+
+    /** Creation time. */
+    private long createTime;
+
+    /** Last access time. */
+    private long accessTime;
+
+    /** Maximum inactive interval. */
+    private int maxInactiveInterval;
+
+    /** Attributes. */
+    @GridToStringExclude
+    private Map<String, byte[]> attrs;
+
+    /**
+     * Constructor.
+     */
+    public WebSessionEntity() {
+        // No-op.
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param id Session ID.
+     * @param createTime Session create time.
+     * @param accessTime Session last access time.
+     * @param maxInactiveInterval Session will be removed if not accessed more then this value.
+     */
+    public WebSessionEntity(final String id, final long createTime, final long accessTime,
+        final int maxInactiveInterval) {
+        this.id = id;
+        this.createTime = createTime;
+        this.accessTime = accessTime;
+        this.maxInactiveInterval = maxInactiveInterval;
+    }
+
+    /**
+     * Constructor.
+     */
+    public WebSessionEntity(final WebSessionEntity other) {
+        this(other.id(), other.createTime(), other.accessTime(), other.maxInactiveInterval());
+
+        if (!other.attributes().isEmpty())
+            attrs = new HashMap<>(other.attributes());
+    }
+
+    /**
+     * @return Session ID.
+     */
+    public String id() {
+        return id;
+    }
+
+    /**
+     * @return Create time.
+     */
+    public long createTime() {
+        return createTime;
+    }
+
+    /**
+     * @return Access time.
+     */
+    public long accessTime() {
+        return accessTime;
+    }
+
+    /**
+     * Set access time.
+     *
+     * @param accessTime Access time.
+     */
+    public void accessTime(final long accessTime) {
+        this.accessTime = accessTime;
+    }
+
+    /**
+     * @return Max inactive interval.
+     */
+    public int maxInactiveInterval() {
+        return maxInactiveInterval;
+    }
+
+    /**
+     * Set max inactive interval;
+     *
+     * @param maxInactiveInterval Max inactive interval.
+     */
+    public void maxInactiveInterval(final int maxInactiveInterval) {
+        this.maxInactiveInterval = maxInactiveInterval;
+    }
+
+    /**
+     * @return Session attributes or {@link Collections#emptyMap()}.
+     */
+    public Map<String, byte[]> attributes() {
+        return attrs == null ? Collections.<String, byte[]>emptyMap() : attrs;
+    }
+
+    /**
+     * Add attribute to attribute map.
+     *
+     * @param name Attribute name.
+     * @param val Attribute value.
+     */
+    public void putAttribute(final String name, final byte[] val) {
+        if (attrs == null)
+            attrs = new HashMap<>();
+
+        attrs.put(name, val);
+    }
+
+    /**
+     * Remove attribute.
+     *
+     * @param name Attribute name.
+     */
+    public void removeAttribute(final String name) {
+        if (attrs != null)
+            attrs.remove(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(final BinaryWriter writer) throws BinaryObjectException {
+        final BinaryRawWriter rawWriter = writer.rawWriter();
+
+        rawWriter.writeString(id);
+        rawWriter.writeLong(createTime);
+        rawWriter.writeLong(accessTime);
+        rawWriter.writeInt(maxInactiveInterval);
+        rawWriter.writeMap(attrs);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(final BinaryReader reader) throws BinaryObjectException {
+        final BinaryRawReader rawReader = reader.rawReader();
+
+        id = rawReader.readString();
+        createTime = rawReader.readLong();
+        accessTime = rawReader.readLong();
+        maxInactiveInterval = rawReader.readInt();
+        attrs = rawReader.readMap();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(WebSessionEntity.class, this,
+            "attributes", attrs != null ? attrs.keySet() : Collections.<String>emptySet());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSession.java
----------------------------------------------------------------------
diff --git a/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSession.java b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSession.java
index 05ecc36..5734b64 100644
--- a/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSession.java
+++ b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSession.java
@@ -76,9 +76,8 @@ class WebSession implements HttpSession, Externalizable {
     @GridToStringExclude
     private transient ServletContext ctx;
 
-    /** Listener. */
     @GridToStringExclude
-    private transient WebSessionListener lsnr;
+    private transient WebSessionFilter filter;
 
     /** New session flag. */
     private transient boolean isNew;
@@ -140,12 +139,12 @@ class WebSession implements HttpSession, Externalizable {
     }
 
     /**
-     * @param lsnr Listener.
+     * @param filter Filter.
      */
-    public void listener(WebSessionListener lsnr) {
-        assert lsnr != null;
+    public void filter(final WebSessionFilter filter) {
+        assert filter != null;
 
-        this.lsnr = lsnr;
+        this.filter = filter;
     }
 
     /**
@@ -248,7 +247,7 @@ class WebSession implements HttpSession, Externalizable {
 
         updates = null;
 
-        lsnr.destroySession(id);
+        filter.destroySession(id);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionFilter.java
----------------------------------------------------------------------
diff --git a/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionFilter.java b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionFilter.java
index 02b5f65..c0f62bf 100644
--- a/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionFilter.java
+++ b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionFilter.java
@@ -19,6 +19,7 @@ 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;
@@ -42,13 +43,17 @@ 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;
 
@@ -182,21 +187,28 @@ public class WebSessionFilter implements Filter {
     /** 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;
 
+    // TOOD: Minimal JavaDoc.
+    /** */
+    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;
 
-    /** Listener. */
-    private WebSessionListener lsnr;
-
     /** Logger. */
     private IgniteLogger log;
 
@@ -221,6 +233,12 @@ public class WebSessionFilter implements Filter {
     /** */
     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();
@@ -256,6 +274,11 @@ public class WebSessionFilter implements Filter {
             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(gridName);
 
         if (webSesIgnite == null)
@@ -266,9 +289,9 @@ public class WebSessionFilter implements Filter {
 
         log = webSesIgnite.log();
 
-        initCache();
+        marshaller = webSesIgnite.configuration().getMarshaller();
 
-        lsnr = new WebSessionListener(webSesIgnite, this, retries);
+        initCache();
 
         String srvInfo = ctx.getServerInfo();
 
@@ -304,17 +327,12 @@ public class WebSessionFilter implements Filter {
     }
 
     /**
-     * @return Cache.
-     */
-    IgniteCache<String, WebSession> getCache(){
-        return cache;
-    }
-
-    /**
      * 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);
@@ -357,13 +375,13 @@ public class WebSessionFilter implements Filter {
             try {
                 if (txEnabled) {
                     try (Transaction tx = txs.txStart(PESSIMISTIC, REPEATABLE_READ)) {
-                        sesId = doFilter0(httpReq, res, chain);
+                        sesId = doFilterDispatch(httpReq, res, chain);
 
                         tx.commit();
                     }
                 }
                 else
-                    sesId = doFilter0(httpReq, res, chain);
+                    sesId = doFilterDispatch(httpReq, res, chain);
             }
             catch (Exception e) {
                 U.error(log, "Failed to update web session: " + sesId, e);
@@ -374,6 +392,25 @@ public class WebSessionFilter implements Filter {
     }
 
     /**
+     * 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.
@@ -382,32 +419,23 @@ public class WebSessionFilter implements Filter {
      * @throws ServletException In case oif servlet error.
      * @throws CacheException In case of other error.
      */
-    private String doFilter0(HttpServletRequest httpReq, ServletResponse res, FilterChain chain) throws IOException,
+    private String doFilterV1(HttpServletRequest httpReq, ServletResponse res, FilterChain chain) throws IOException,
         ServletException, CacheException {
         WebSession cached = null;
 
         String sesId = httpReq.getRequestedSessionId();
 
         if (sesId != null) {
-            if (sesIdTransformer != null)
-                sesId = sesIdTransformer.apply(sesId);
+            sesId = transformSessionId(sesId);
 
             for (int i = 0; i < retries; i++) {
                 try {
                     cached = cache.get(sesId);
+
+                    break;
                 }
                 catch (CacheException | IgniteException | IllegalStateException e) {
-                    if (log.isDebugEnabled())
-                        log.debug(e.getMessage());
-
-                    if (i == 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);
-                    }
+                    handleLoadSessionException(sesId, i, e);
                 }
             }
 
@@ -445,23 +473,132 @@ public class WebSessionFilter implements Filter {
         assert cached != null;
 
         cached.servletContext(ctx);
-        cached.listener(lsnr);
+        cached.filter(this);
         cached.resetUpdates();
 
         httpReq = new RequestWrapper(httpReq, cached);
 
         chain.doFilter(httpReq, res);
 
-        HttpSession ses = httpReq.getSession(false);
+        final Collection<T2<String, Object>> updates = cached.updates();
+
+        if (updates != null)
+            updateAttributes(transformSessionId(sesId), updates, cached.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, null, 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 && ses instanceof WebSession) {
-            Collection<T2<String, Object>> updates = ((WebSession)ses).updates();
+                if (ses != null) {
+                    try {
+                        ses.invalidate();
+                    }
+                    catch (IllegalStateException ignore) {
+                        // Session was already invalidated.
+                    }
+                }
 
-            if (updates != null) {
-                lsnr.updateAttributes(sesIdTransformer != null ? sesIdTransformer.apply(ses.getId()) : ses.getId(),
-                    updates, ses.getMaxInactiveInterval());
+                cached = createSessionV2(httpReq);
             }
         }
+        // No session was requested by the client, create new one and put in the request.
+        else {
+            cached = createSessionV2(httpReq);
+
+            sesId = cached.getId();
+        }
+
+        assert cached != null;
+
+        httpReq = new RequestWrapperV2(httpReq, cached);
+
+        chain.doFilter(httpReq, res);
+
+        // Update session
+        if (cached.isValid())
+            updateAttributesV2(sesId, cached);
+        else
+            binaryCache.remove(sesId);
+
+        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;
     }
@@ -474,7 +611,7 @@ public class WebSessionFilter implements Filter {
     private WebSession createSession(HttpServletRequest httpReq) {
         HttpSession ses = httpReq.getSession(true);
 
-        String sesId = sesIdTransformer != null ? sesIdTransformer.apply(ses.getId()) : ses.getId();
+        String sesId = transformSessionId(ses.getId());
 
         if (log.isDebugEnabled())
             log.debug("Session created: " + sesId);
@@ -483,19 +620,10 @@ public class WebSessionFilter implements Filter {
 
         for (int i = 0; i < retries; i++) {
             try {
-                IgniteCache<String, WebSession> cache0;
-
-                if (cached.getMaxInactiveInterval() > 0) {
-                    long ttl = cached.getMaxInactiveInterval() * 1000;
+                final IgniteCache<String, WebSession> cache0 =
+                    cacheWithExpiryPolicy(cached.getMaxInactiveInterval(), cache);
 
-                    ExpiryPolicy plc = new ModifiedExpiryPolicy(new Duration(MILLISECONDS, ttl));
-
-                    cache0 = cache.withExpiryPolicy(plc);
-                }
-                else
-                    cache0 = cache;
-
-                WebSession old = cache0.getAndPutIfAbsent(sesId, cached);
+                final WebSession old = cache0.getAndPutIfAbsent(sesId, cached);
 
                 if (old != null) {
                     cached = old;
@@ -507,27 +635,206 @@ public class WebSessionFilter implements Filter {
                 break;
             }
             catch (CacheException | IgniteException | IllegalStateException e) {
-                if (log.isDebugEnabled())
-                    log.debug(e.getMessage());
+                handleCreateSessionException(sesId, i, e);
+            }
+        }
 
-                if (i == retries - 1)
-                    throw new IgniteException("Failed to save session: " + sesId, 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);
+        }
+    }
+
+    /**
+     * @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);
+
+        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, null, false, ctx, old, marshaller);
+                else
+                    cached = new WebSessionV2(sesId, null, false, ctx, marshaledEntity, marshaller);
+
+                break;
+            }
+            catch (CacheException | IgniteException | IllegalStateException e) {
+                handleCreateSessionException(sesId, i, e);
+            }
+        }
+
+        return cached;
+    }
+
+    /**
+     * @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 {
-                    if (log.isDebugEnabled())
-                        log.debug("Failed to save session (will retry): " + sesId);
+                    U.warn(log, "Failed to remove session (will retry): " + sesId);
 
                     handleCacheOperationException(e);
                 }
             }
         }
+    }
 
-        return cached;
+    /**
+     * @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.warn(log, "Failed to apply updates for session (maximum number of retries exceeded) [sesId=" +
+                sesId + ", retries=" + retries + ']');
+        }
+        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;
 
@@ -595,4 +902,34 @@ public class WebSessionFilter implements Filter {
             return ses;
         }
     }
+
+    /**
+     * Request wrapper V2.
+     */
+    private static class RequestWrapperV2 extends HttpServletRequestWrapper {
+        /** Session. */
+        private final 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) {
+            return ses;
+        }
+
+        /** {@inheritDoc} */
+        @Override public HttpSession getSession() {
+            return ses;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionListener.java
----------------------------------------------------------------------
diff --git a/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionListener.java b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionListener.java
index 0d8ffec..ab41879 100644
--- a/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionListener.java
+++ b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionListener.java
@@ -22,127 +22,24 @@ import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
 import java.util.Collection;
-import javax.cache.CacheException;
-import javax.cache.expiry.Duration;
-import javax.cache.expiry.ExpiryPolicy;
-import javax.cache.expiry.ModifiedExpiryPolicy;
 import javax.cache.processor.EntryProcessor;
 import javax.cache.processor.MutableEntry;
-import org.apache.ignite.Ignite;
-import org.apache.ignite.IgniteCache;
-import org.apache.ignite.IgniteException;
-import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.util.typedef.T2;
-import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
 /**
  * Session listener for web sessions caching.
  */
 class WebSessionListener {
-    /** Filter. */
-    private final WebSessionFilter filter;
-
-    /** Maximum retries. */
-    private final int retries;
-
-    /** Logger. */
-    private final IgniteLogger log;
-
-    /**
-     * @param ignite Grid.
-     * @param filter Filter.
-     * @param retries Maximum retries.
-     */
-    WebSessionListener(Ignite ignite, WebSessionFilter filter, int retries) {
-        assert ignite != null;
-        assert filter != null;
-
-        this.filter = filter;
-        this.retries = retries > 0 ? retries : 1;
-
-        log = ignite.log();
-    }
-
-    /**
-     * @param sesId Session ID.
-     */
-    public void destroySession(String sesId) {
-        assert sesId != null;
-        for (int i = 0; i < retries; i++) {
-            try {
-                if (filter.getCache().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);
-
-                    filter.handleCacheOperationException(e);
-                }
-            }
-        }
-    }
-
     /**
-     * @param sesId Session ID.
-     * @param updates Updates list.
-     * @param maxInactiveInterval Max session inactive interval.
+     * Creates new instance of attribute processor. Used for compatibility.
+     *
+     * @param updates Updates.
+     * @return New instance of attribute processor.
      */
-    @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 {
-                    IgniteCache<String, WebSession> cache0;
-
-                    if (maxInactiveInterval > 0) {
-                        long ttl = maxInactiveInterval * 1000;
-
-                        ExpiryPolicy plc = new ModifiedExpiryPolicy(new Duration(MILLISECONDS, ttl));
-
-                        cache0 = filter.getCache().withExpiryPolicy(plc);
-                    }
-                    else
-                        cache0 = filter.getCache();
-
-                    cache0.invoke(sesId, new AttributesProcessor(updates));
-
-                    break;
-                }
-                catch (CacheException | IgniteException | IllegalStateException e) {
-                    if (i == retries - 1) {
-                        U.warn(log, "Failed to apply updates for session (maximum number of retries exceeded) [sesId=" +
-                            sesId + ", retries=" + retries + ']');
-                    }
-                    else {
-                        U.warn(log, "Failed to apply updates for session (will retry): " + sesId);
-
-                        filter.handleCacheOperationException(e);
-                    }
-                }
-            }
-        }
-        catch (Exception e) {
-            U.error(log, "Failed to update session attributes [id=" + sesId + ']', e);
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return S.toString(WebSessionListener.class, this);
+    public static EntryProcessor<String, WebSession, Void> newAttributeProcessor(
+        final Collection<T2<String, Object>> updates) {
+        return new AttributesProcessor(updates);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionV2.java
----------------------------------------------------------------------
diff --git a/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionV2.java b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionV2.java
new file mode 100644
index 0000000..53f97a2
--- /dev/null
+++ b/modules/web/src/main/java/org/apache/ignite/cache/websession/WebSessionV2.java
@@ -0,0 +1,397 @@
+/*
+ * 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 org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.util.tostring.GridToStringExclude;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.websession.WebSessionEntity;
+import org.apache.ignite.marshaller.Marshaller;
+import org.jetbrains.annotations.Nullable;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Session implementation that uses internal entity, which stores binary attributes,
+ * for safe caching and processing on remote nodes.
+ */
+class WebSessionV2 implements HttpSession {
+    /** Empty session context. */
+    @SuppressWarnings("deprecation")
+    private static final HttpSessionContext EMPTY_SES_CTX = new HttpSessionContext() {
+        @Nullable @Override public HttpSession getSession(String id) {
+            return null;
+        }
+
+        @Override public Enumeration<String> getIds() {
+            return Collections.enumeration(Collections.<String>emptyList());
+        }
+    };
+
+    /** Placeholder for removed attribute. */
+    private static final Object REMOVED_ATTR = new Object();
+
+    /** Servlet context. */
+    @GridToStringExclude
+    private final ServletContext ctx;
+
+    /** Entity that holds binary attributes. */
+    private WebSessionEntity entity;
+
+    /** Attributes. */
+    protected Map<String, Object> attrs;
+
+    /** Attributes waiting for update in cache. */
+    private Map<String, Object> updatesMap;
+
+    /** Timestamp that shows when this object was created. (Last access time from user request) */
+    private final long accessTime;
+
+    /** Cached session TTL since last query. */
+    private int maxInactiveInterval;
+
+    /** Flag indicates if {@link #maxInactiveInterval} waiting for update in cache. */
+    private boolean maxInactiveIntervalChanged;
+
+    /** New session flag. */
+    private boolean isNew;
+
+    /** Session invalidation flag. */
+    private boolean invalidated;
+
+    /** Grid marshaller. */
+    private final Marshaller marshaller;
+
+    /**
+     * @param id Session ID.
+     * @param ses Session.
+     * @param isNew Is new flag.
+     */
+    WebSessionV2(final String id, final @Nullable HttpSession ses, final boolean isNew, final ServletContext ctx,
+        @Nullable WebSessionEntity entity, final Marshaller marshaller) {
+        assert id != null;
+        assert marshaller != null;
+        assert ctx != null;
+        assert ses != null || entity != null;
+
+        this.marshaller = marshaller;
+        this.ctx = ctx;
+        this.isNew = isNew;
+
+        accessTime = System.currentTimeMillis();
+
+        if (entity == null) {
+            entity = new WebSessionEntity(id, ses.getCreationTime(), accessTime,
+                ses.getMaxInactiveInterval());
+        }
+
+        this.entity = entity;
+
+        maxInactiveInterval = entity.maxInactiveInterval();
+
+        if (ses != null) {
+            final Enumeration<String> names = ses.getAttributeNames();
+
+            while (names.hasMoreElements()) {
+                final String name = names.nextElement();
+
+                attributes().put(name, ses.getAttribute(name));
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getCreationTime() {
+        assertValid();
+
+        return entity.createTime();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getId() {
+        assertValid();
+
+        return entity.id();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getLastAccessedTime() {
+        assertValid();
+
+        return accessTime;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ServletContext getServletContext() {
+        return ctx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setMaxInactiveInterval(final int interval) {
+        maxInactiveInterval = interval;
+
+        maxInactiveIntervalChanged = true;
+    }
+
+    /**
+     * @return {@code True} if {@link #setMaxInactiveInterval(int)} was invoked.
+     */
+    public boolean isMaxInactiveIntervalChanged() {
+        return maxInactiveIntervalChanged;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxInactiveInterval() {
+        return maxInactiveInterval;
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("deprecation")
+    @Override public HttpSessionContext getSessionContext() {
+        return EMPTY_SES_CTX;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object getAttribute(final String name) {
+        assertValid();
+
+        Object attr = attributes().get(name);
+
+        if (attr == REMOVED_ATTR)
+            return null;
+
+        if (attr != null)
+            return attr;
+
+        final byte[] bytes = entity.attributes().get(name);
+
+        if (bytes != null) {
+            // deserialize
+            try {
+                attr = unmarshal(bytes);
+            }
+            catch (IOException e) {
+                throw new IgniteException(e);
+            }
+
+            attributes().put(name, attr);
+        }
+
+        return attr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object getValue(final String name) {
+        return getAttribute(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setAttribute(final String name, final Object val) {
+        assertValid();
+
+        attributes().put(name, val);
+
+        updatesMap().put(name, val);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void putValue(final String name, final Object val) {
+        setAttribute(name, val);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Enumeration<String> getAttributeNames() {
+        assertValid();
+
+        return Collections.enumeration(attributeNames());
+    }
+
+    /**
+     * @return Set of attribute names.
+     */
+    private Set<String> attributeNames() {
+        if (!F.isEmpty(attrs)) {
+            final Set<String> names = new HashSet<>(entity.attributes().size() + attrs.size());
+
+            names.addAll(entity.attributes().keySet());
+
+            for (final Map.Entry<String, Object> entry : attrs.entrySet()) {
+                if (entry != REMOVED_ATTR)
+                    names.add(entry.getKey());
+            }
+
+            return names;
+        }
+
+        return entity.attributes().keySet();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String[] getValueNames() {
+        assertValid();
+
+        final Set<String> names = attributeNames();
+
+        return names.toArray(new String[names.size()]);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void removeAttribute(final String name) {
+        assertValid();
+
+        attributes().put(name, REMOVED_ATTR);
+
+        updatesMap().put(name, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void removeValue(final String name) {
+        removeAttribute(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void invalidate() {
+        assertValid();
+
+        invalidated = true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isNew() {
+        assertValid();
+
+        return isNew;
+    }
+
+    /**
+     * @return Marshaled updates or empty map if no params.
+     * @throws IOException
+     */
+    public Map<String, byte[]> binaryUpdatesMap() throws IOException {
+        final Map<String, Object> map = updatesMap;
+
+        if (F.isEmpty(map))
+            return Collections.emptyMap();
+
+        final Map<String, byte[]> res = new HashMap<>(map.size());
+
+        for (final Map.Entry<String, Object> entry : map.entrySet())
+            res.put(entry.getKey(), marshal(entry.getValue()));
+
+        return res;
+    }
+
+    /**
+     * Unmarshal object.
+     *
+     * @param bytes Data.
+     * @param <T> Expected type.
+     * @return Unmarshaled object.
+     * @throws IOException If unarshaling failed.
+     */
+    @Nullable private <T> T unmarshal(final byte[] bytes) throws IOException {
+        if (marshaller != null) {
+            try {
+                return marshaller.unmarshal(bytes, getClass().getClassLoader());
+            }
+            catch (IgniteCheckedException e) {
+                throw new IOException(e);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Marshal object.
+     *
+     * @param obj Object to marshal.
+     * @return Binary data.
+     * @throws IOException If marshaling failed.
+     */
+    @Nullable private byte[] marshal(final Object obj) throws IOException {
+        if (marshaller != null) {
+            try {
+                return marshaller.marshal(obj);
+            }
+            catch (IgniteCheckedException e) {
+                throw new IOException(e);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Marshal all attributes and save to serializable entity.
+     */
+    public WebSessionEntity marshalAttributes() throws IOException {
+        final WebSessionEntity marshaled = new WebSessionEntity(getId(), entity.createTime(), accessTime,
+            maxInactiveInterval);
+
+        for (final Map.Entry<String, Object> entry : attributes().entrySet())
+            marshaled.attributes().put(entry.getKey(), marshal(entry.getValue()));
+
+        return marshaled;
+    }
+
+    /**
+     * @return Session attributes.
+     */
+    private Map<String, Object> attributes() {
+        if (attrs == null)
+            attrs = new HashMap<>();
+
+        return attrs;
+    }
+
+    /**
+     * @return Updates map.
+     */
+    private Map<String, Object> updatesMap() {
+        if (updatesMap == null)
+            updatesMap = new HashMap<>();
+
+        return updatesMap;
+    }
+
+    /**
+     * @return {@code True} if session wasn't invalidated.
+     */
+    public boolean isValid() {
+        return !invalidated;
+    }
+
+    /**
+     * Throw {@link IllegalStateException} if session was invalidated.
+     */
+    private void assertValid() {
+        if (invalidated)
+            throw new IllegalStateException("Session was invalidated.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java b/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java
index 90cb132..a312b3c 100644
--- a/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java
+++ b/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java
@@ -18,8 +18,11 @@
 package org.apache.ignite.internal.websession;
 
 import java.io.BufferedReader;
+import java.io.Externalizable;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.Random;
@@ -39,6 +42,7 @@ import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.util.typedef.G;
 import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.marshaller.Marshaller;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.eclipse.jetty.server.Server;
@@ -56,6 +60,9 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
     /** Servers count in load test. */
     private static final int SRV_CNT = 3;
 
+    /** */
+    private static boolean keepBinaryFlag = true;
+
     /**
      * @return Name of the cache for this test.
      */
@@ -63,6 +70,13 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
         return "partitioned";
     }
 
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        keepBinaryFlag = true;
+    }
+
     /**
      * @throws Exception If failed.
      */
@@ -71,6 +85,15 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
     }
 
     /**
+     * @throws Exception
+     */
+    public void testSingleRequestNoKeepBinary() throws Exception {
+        keepBinaryFlag = false;
+
+        testSingleRequest("/modules/core/src/test/config/websession/example-cache.xml");
+    }
+
+    /**
      * @throws Exception If failed.
      */
     public void testSingleRequestMetaInf() throws Exception {
@@ -99,7 +122,7 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
         Ignite ignite = Ignition.start(srvCfg);
 
         try {
-            srv = startServer(TEST_JETTY_PORT, clientCfg, "client", new SessionCreateServlet());
+            srv = startServer(TEST_JETTY_PORT, clientCfg, "client", keepBinaryFlag, new SessionCreateServlet());
 
             URL url = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/test");
 
@@ -158,7 +181,7 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
         Server srv = null;
 
         try {
-            srv = startServer(TEST_JETTY_PORT, cfg, null, new SessionCreateServlet());
+            srv = startServer(TEST_JETTY_PORT, cfg, null, keepBinaryFlag, new SessionCreateServlet());
 
             URLConnection conn = new URL("http://localhost:" + TEST_JETTY_PORT + "/ignitetest/test").openConnection();
 
@@ -167,14 +190,36 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
             try (BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
                 String sesId = rdr.readLine();
 
-                IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName());
+                if (!keepBinaryFlag) {
+                    IgniteCache<String, HttpSession> cache = G.ignite().cache(getCacheName());
+
+                    assertNotNull(cache);
+
+                    HttpSession ses = cache.get(sesId);
+
+                    assertNotNull(ses);
 
-                assertNotNull(cache);
+                    assertEquals("val1", ses.getAttribute("key1"));
+                }
+                else {
+                    final IgniteCache<String, WebSessionEntity> cache = G.ignite().cache(getCacheName());
+
+                    assertNotNull(cache);
+
+                    final WebSessionEntity entity = cache.get(sesId);
+
+                    assertNotNull(entity);
+
+                    final byte[] data = entity.attributes().get("key1");
+
+                    assertNotNull(data);
+
+                    final Marshaller marshaller = G.ignite().configuration().getMarshaller();
 
-                HttpSession ses = cache.get(sesId);
+                    final String val = marshaller.unmarshal(data, getClass().getClassLoader());
 
-                assertNotNull(ses);
-                assertEquals("val1", ses.getAttribute("key1"));
+                    assertEquals("val1", val);
+                }
             }
         }
         finally {
@@ -194,7 +239,7 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
             String cfg = "/modules/core/src/test/config/websession/spring-cache-" + (idx + 1) + ".xml";
 
             srvs.set(idx, startServer(
-                TEST_JETTY_PORT + idx, cfg, "grid-" + (idx + 1), new RestartsTestServlet(sesIdRef)));
+                TEST_JETTY_PORT + idx, cfg, "grid-" + (idx + 1), keepBinaryFlag, new RestartsTestServlet(sesIdRef)));
         }
 
         final AtomicBoolean stop = new AtomicBoolean();
@@ -221,7 +266,7 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
                     String cfg = "/modules/core/src/test/config/websession/spring-cache-" + (idx + 1) + ".xml";
 
                     srv = startServer(
-                        TEST_JETTY_PORT + idx, cfg, "grid-" + (idx + 1), new RestartsTestServlet(sesIdRef));
+                        TEST_JETTY_PORT + idx, cfg, "grid-" + (idx + 1), keepBinaryFlag, new RestartsTestServlet(sesIdRef));
 
                     assert srvs.compareAndSet(idx, null, srv);
 
@@ -299,14 +344,18 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
      * @param servlet Servlet.
      * @return Servlet container web context for this test.
      */
-    protected WebAppContext getWebContext(@Nullable String cfg, @Nullable String gridName, HttpServlet servlet) {
-        WebAppContext ctx = new WebAppContext(U.resolveIgnitePath("modules/core/src/test/webapp").getAbsolutePath(),
+    protected WebAppContext getWebContext(@Nullable String cfg, @Nullable String gridName,
+        boolean keepBinaryFlag, HttpServlet servlet) {
+        final String path = keepBinaryFlag ? "modules/core/src/test/webapp" : "modules/web/src/test/webapp2";
+
+        WebAppContext ctx = new WebAppContext(U.resolveIgnitePath(path).getAbsolutePath(),
             "/ignitetest");
 
         ctx.setInitParameter("IgniteConfigurationFilePath", cfg);
         ctx.setInitParameter("IgniteWebSessionsGridName", gridName);
         ctx.setInitParameter("IgniteWebSessionsCacheName", getCacheName());
         ctx.setInitParameter("IgniteWebSessionsMaximumRetriesOnFail", "100");
+        ctx.setInitParameter("IgniteWebSessionsKeepBinary", Boolean.toString(keepBinaryFlag));
 
         ctx.addServlet(new ServletHolder(servlet), "/*");
 
@@ -323,11 +372,12 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
      * @return Server.
      * @throws Exception In case of error.
      */
-    private Server startServer(int port, @Nullable String cfg, @Nullable String gridName, HttpServlet servlet)
+    private Server startServer(int port, @Nullable String cfg, @Nullable String gridName,
+        boolean keepBinaryFlag, HttpServlet servlet)
         throws Exception {
         Server srv = new Server(port);
 
-        WebAppContext ctx = getWebContext(cfg, gridName, servlet);
+        WebAppContext ctx = getWebContext(cfg, gridName, keepBinaryFlag, servlet);
 
         srv.setHandler(ctx);
 
@@ -359,6 +409,7 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
             ses.setAttribute("checkCnt", 0);
             ses.setAttribute("key1", "val1");
             ses.setAttribute("key2", "val2");
+            ses.setAttribute("mkey", new TestObj("mval"));
 
             X.println(">>>", "Created session: " + ses.getId(), ">>>");
 
@@ -403,4 +454,60 @@ public class WebSessionSelfTest extends GridCommonAbstractTest {
             res.getWriter().flush();
         }
     }
+
+    /**
+     *
+     */
+    private static class TestObj implements Externalizable {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /** */
+        private String val;
+
+        /**
+         *
+         */
+        public TestObj() {
+        }
+
+        /**
+         * @param val Value.
+         */
+        public TestObj(final String val) {
+            this.val = val;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void writeExternal(final ObjectOutput out) throws IOException {
+            U.writeString(out, val);
+            System.out.println("TestObj marshalled");
+        }
+
+        /** {@inheritDoc} */
+        @Override public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+            // It must be unmarshalled only on client side.
+            if (keepBinaryFlag)
+                fail("Should not be unmarshalled");
+
+            val = U.readString(in);
+            System.out.println("TestObj unmarshalled");
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(final Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            final TestObj testObj = (TestObj) o;
+
+            return val != null ? val.equals(testObj.val) : testObj.val == null;
+
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return val != null ? val.hashCode() : 0;
+        }
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/web/src/test/webapp2/META-INF/ignite-webapp-config.xml
----------------------------------------------------------------------
diff --git a/modules/web/src/test/webapp2/META-INF/ignite-webapp-config.xml b/modules/web/src/test/webapp2/META-INF/ignite-webapp-config.xml
new file mode 100644
index 0000000..7fdd559
--- /dev/null
+++ b/modules/web/src/test/webapp2/META-INF/ignite-webapp-config.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    Ignite Spring configuration file to startup grid cache.
+
+    When starting a standalone Ignite node, you need to execute the following command:
+    {IGNITE_HOME}/bin/ignite.{bat|sh} examples/config/spring-cache.xml
+
+    When starting Ignite from Java IDE, pass path to this file into Ignition:
+    Ignition.start("examples/config/spring-cache.xml");
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd
+        http://www.springframework.org/schema/util
+        http://www.springframework.org/schema/util/spring-util.xsd">
+    <!--
+        Configuration below demonstrates how to setup caches within grid nodes.
+    -->
+    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+        <property name="deploymentMode" value="SHARED"/>
+
+        <!--
+            For better performance set this property to false in case
+            peer deployment is not used.
+            Default value is false.
+        -->
+        <property name="peerClassLoadingEnabled" value="true"/>
+
+        <!--
+            Configure optimized marshaller.
+        -->
+        <property name="marshaller">
+            <bean class="org.apache.ignite.marshaller.optimized.OptimizedMarshaller">
+                <!--
+                    For better performance set this property to true in case
+                    all marshalled classes implement java.io.Serializable.
+                    Default value is true.
+
+                    Note, that it is recommended to implement java.io.Externalizable
+                    instead of java.io.Serializable for smaller network footprint
+                    and even better performance.
+                -->
+                <property name="requireSerializable" value="false"/>
+            </bean>
+        </property>
+
+        <!-- Set to local host address just for examples. -->
+        <property name="localHost" value="127.0.0.1"/>
+
+        <!-- Configure REST TCP server address. -->
+        <property name="connectorConfiguration">
+            <bean class="org.apache.ignite.configuration.ConnectorConfiguration">
+                <property name="host" value="127.0.0.1"/>
+            </bean>
+        </property>
+
+        <!--
+            Enable cache events.
+        -->
+        <property name="includeEventTypes">
+            <util:constant static-field="org.apache.ignite.events.EventType.EVTS_CACHE"/>
+        </property>
+
+        <property name="cacheConfiguration">
+            <!--
+                Specify list of cache configurations here. Any property from
+                CacheConfiguration interface can be configured here.
+                Note that absolutely all configuration properties are optional.
+            -->
+            <list>
+                <!--
+                    Partitioned cache example configuration (Atomic mode).
+                -->
+                <bean class="org.apache.ignite.configuration.CacheConfiguration">
+                    <property name="name" value="partitioned"/>
+
+                    <property name="cacheMode" value="PARTITIONED"/>
+
+                    <!-- Only atomic updates will be supported. -->
+                    <property name="atomicityMode" value="ATOMIC"/>
+
+                    <!-- Enable primary sync write mode. -->
+                    <property name="writeSynchronizationMode" value="PRIMARY_SYNC"/>
+
+                    <!-- Initial cache size. -->
+                    <property name="startSize" value="1500000"/>
+
+                    <!--
+                        This shows how to configure number of backups. The below configuration
+                        sets the number of backups to 1 (which is default).
+                    -->
+                    <property name="backups" value="1"/>
+
+                    <!-- Set synchronous rebalancing (default is asynchronous). -->
+                    <property name="rebalanceMode" value="SYNC"/>
+                </bean>
+
+                <!--
+                    Partitioned cache example configuration (Transactional mode).
+                -->
+                <bean class="org.apache.ignite.configuration.CacheConfiguration">
+                    <property name="name" value="partitioned_tx"/>
+
+                    <property name="cacheMode" value="PARTITIONED"/>
+
+                    <!-- Transactional updates supported. -->
+                    <property name="atomicityMode" value="TRANSACTIONAL"/>
+
+                    <!-- Enable near cache to cache recently accessed data. -->
+                    <property name="nearConfiguration">
+                        <bean class="org.apache.ignite.configuration.NearCacheConfiguration"/>
+                    </property>
+
+                    <!-- Initial cache size. -->
+                    <property name="startSize" value="1500000"/>
+
+                    <!--
+                        Setting this value will cause local node to wait for remote commits.
+                        However, it's important to set it this way in the examples as we assert on
+                        conditions that usually assume full completion of transactions on all nodes.
+                    -->
+                    <property name="writeSynchronizationMode" value="FULL_SYNC"/>
+
+                    <!--
+                        This shows how to configure number of backups. The below configuration
+                        sets the number of backups to 1 (which is default).
+                    -->
+                    <property name="backups" value="1"/>
+
+                    <!-- Set synchronous rebalancing (default is asynchronous). -->
+                    <property name="rebalanceMode" value="SYNC"/>
+                </bean>
+
+                <!--
+                    Replicated cache example configuration.
+                -->
+                <bean class="org.apache.ignite.configuration.CacheConfiguration">
+                    <property name="name" value="replicated"/>
+
+                    <!--
+                        Setting this value will cause local node to wait for remote commits.
+                        However, it's important to set it this way in the examples as we assert on
+                        conditions that usually assume full completion of transactions on all nodes.
+                    -->
+                    <property name="writeSynchronizationMode" value="FULL_SYNC"/>
+
+                    <!-- REPLICATED cache mode. -->
+                    <property name="cacheMode" value="REPLICATED"/>
+
+                    <!-- Set synchronous rebalancing (default is asynchronous). -->
+                    <property name="rebalanceMode" value="SYNC"/>
+
+                    <!-- Initial cache size. -->
+                    <property name="startSize" value="150000"/>
+                </bean>
+
+                <!--
+                    Local cache example configuration.
+                -->
+                <bean class="org.apache.ignite.configuration.CacheConfiguration">
+                    <!-- Cache name is 'local'. -->
+                    <property name="name" value="local"/>
+
+                    <!-- LOCAL cache mode. -->
+                    <property name="cacheMode" value="LOCAL"/>
+
+                    <!-- Initial cache size. -->
+                    <property name="startSize" value="150000"/>
+                </bean>
+            </list>
+        </property>
+
+        <!--
+            Uncomment this to provide TCP discovery SPI (Amazon EC2).
+        -->
+        <!--
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.s3.TcpDiscoveryS3IpFinder">
+                        <property name="awsCredentials">
+                            <bean class="com.amazonaws.auth.BasicAWSCredentials">
+                                <constructor-arg value="YOUR_ACCESS_KEY_ID" />
+                                <constructor-arg value="YOUR_SECRET_ACCESS_KEY" />
+                            </bean>
+                        </property>
+                        <property name="bucketName" value="YOUR_BUCKET_NAME_IP_FINDER"/>
+                    </bean>
+                </property>
+                <property name="heartbeatFrequency" value="2000"/>
+            </bean>
+        </property>
+        -->
+
+        <!--
+            Uncomment this to provide TCP discovery SPI (Local network).
+
+            If path to shared file system is not explicitly provided,
+            then only local nodes will be able to discover each other.
+        -->
+        <!--
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinder">
+                        <property name="path" value="work/disco/tcp"/>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+        -->
+
+        <!--
+            TCP discovery SPI configuration with predefined addresses.
+            Use the addresses list to provide IP addresses of initial nodes in the grid
+            (at least one address must be provided).
+
+            Note:
+            =====
+            If running in distributed environment, you should change IP addresses to the actual IP addresses
+            of the servers on your network. Not all addresses need to be specified, only the addresses
+            of one or more servers which will always be started first.
+        -->
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+                        <property name="addresses">
+                            <list>
+                                <!--
+                                    List all IP/port configurations that potentially
+                                    can be started first in examples. We are assuming
+                                    grid of size 10 or less.
+                                -->
+                                <value>127.0.0.1:47500</value>
+                                <value>127.0.0.1:47501</value>
+                                <value>127.0.0.1:47502</value>
+                                <value>127.0.0.1:47503</value>
+                                <value>127.0.0.1:47504</value>
+                                <value>127.0.0.1:47505</value>
+                                <value>127.0.0.1:47506</value>
+                                <value>127.0.0.1:47507</value>
+                                <value>127.0.0.1:47508</value>
+                                <value>127.0.0.1:47509</value>
+                            </list>
+                        </property>
+                    </bean>
+                    <!--
+                    Uncomment this to provide IP finder using multicast for nodes discovery.
+                    In addition to addresses received via multicast this finder can work with pre-configured
+                    list of addresses.
+                    -->
+                    <!--
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">
+                        <property name="addresses">
+                            <list>
+                                <value>host1:port1</value>
+                                <value>host2:port2</value>
+                            </list>
+                        </property>
+                    </bean>
+                    -->
+                </property>
+            </bean>
+        </property>
+    </bean>
+</beans>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ee1cd486/modules/web/src/test/webapp2/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/modules/web/src/test/webapp2/WEB-INF/web.xml b/modules/web/src/test/webapp2/WEB-INF/web.xml
new file mode 100644
index 0000000..ad65447
--- /dev/null
+++ b/modules/web/src/test/webapp2/WEB-INF/web.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ Copyright (C) GridGain Systems. All Rights Reserved.
+  ~ _________        _____ __________________        _____
+  ~ __  ____/___________(_)______  /__  ____/______ ____(_)_______
+  ~  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
+  ~  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
+  ~  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
+  -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+                             http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+
+    <!-- Declare listener for web sessions caching. -->
+    <listener>
+        <listener-class>org.apache.ignite.startup.servlet.ServletContextListenerStartup</listener-class>
+    </listener>
+
+    <!-- Declare filter for web sessions caching. -->
+    <filter>
+        <filter-name>IgniteWebSessionsFilter</filter-name>
+        <filter-class>org.apache.ignite.cache.websession.WebSessionFilter</filter-class>
+        <init-param>
+            <param-name>IgniteWebSessionsKeepBinary</param-name>
+            <param-value>false</param-value>
+        </init-param>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>IgniteWebSessionsFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+</web-app>


Mime
View raw message