ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ira...@apache.org
Subject [ignite] branch master updated: IGNITE-5038 BinaryMarshaller might need to use context class loader for deserialization - Fixes #5038
Date Fri, 14 Aug 2020 12:49:00 GMT
This is an automated email from the ASF dual-hosted git repository.

irakov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new b991e3b  IGNITE-5038 BinaryMarshaller might need to use context class loader for deserialization - Fixes #5038
b991e3b is described below

commit b991e3b60f83fe399e4dacb4b8cf5dce99e24e64
Author: Ivan Rakov <ivan.glukos@gmail.com>
AuthorDate: Fri Aug 14 15:48:28 2020 +0300

    IGNITE-5038 BinaryMarshaller might need to use context class loader for deserialization - Fixes #5038
---
 .../org/apache/ignite/binary/BinaryObject.java     |  11 +
 .../internal/binary/BinaryClassDescriptor.java     |  44 ++-
 .../ignite/internal/binary/BinaryContext.java      |  64 ++++-
 .../internal/binary/BinaryEnumObjectImpl.java      |  45 ++-
 .../ignite/internal/binary/BinaryObjectImpl.java   |  16 ++
 .../internal/binary/BinaryObjectOffheapImpl.java   |  31 ++-
 .../ignite/internal/binary/BinaryReaderExImpl.java |   7 +-
 .../apache/ignite/internal/binary/BinaryUtils.java |  42 ++-
 .../internal/binary/GridBinaryMarshaller.java      |   3 +
 .../optimized/OptimizedClassDescriptor.java        |  38 ++-
 .../marshaller/optimized/OptimizedMarshaller.java  |  31 ++-
 .../optimized/OptimizedMarshallerUtils.java        |  68 ++++-
 .../optimized/OptimizedObjectInputStream.java      |  17 +-
 .../optimized/OptimizedObjectOutputStream.java     |   3 +
 .../OptimizedObjectSharedStreamRegistry.java       |  17 ++
 .../apache/ignite/internal/util/IgniteUtils.java   |  27 +-
 .../binary/BinaryClassLoaderMultiJvmTest.java      | 304 +++++++++++++++++++++
 .../internal/binary/BinaryClassLoaderTest.java     | 248 +++++++++++++++++
 .../optimized/OptimizedObjectStreamSelfTest.java   |   2 +-
 .../IgniteBinaryObjectsCacheTestSuite3.java        |   4 +
 .../org/apache/ignite/tests/p2p/cache/Address.java |  57 ++++
 .../org/apache/ignite/tests/p2p/cache/Color.java   |  50 ++++
 .../ignite/tests/p2p/cache/Organization.java       |  49 ++++
 23 files changed, 1141 insertions(+), 37 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/binary/BinaryObject.java b/modules/core/src/main/java/org/apache/ignite/binary/BinaryObject.java
index 8803df9..d6ac766 100644
--- a/modules/core/src/main/java/org/apache/ignite/binary/BinaryObject.java
+++ b/modules/core/src/main/java/org/apache/ignite/binary/BinaryObject.java
@@ -134,6 +134,17 @@ public interface BinaryObject extends Serializable, Cloneable {
     public <T> T deserialize() throws BinaryObjectException;
 
     /**
+     * Gets fully deserialized instance of binary object. If <code>ldr</code> was not specified, configured class loader
+     * will be used {@link org.apache.ignite.configuration.IgniteConfiguration#getClassLoader}.
+     *
+     * @param ldr Class loader.
+     * @return Fully deserialized instance of binary object.
+     * @throws BinaryInvalidTypeException If class doesn't exist.
+     * @throws BinaryObjectException In case of any other error.
+     */
+    public <T> T deserialize(ClassLoader ldr) throws BinaryObjectException;
+
+    /**
      * Copies this binary object.
      *
      * @return Copy of this binary object.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
index 732c4c5..5b58300 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
@@ -151,6 +151,48 @@ public class BinaryClassDescriptor {
         boolean metaDataEnabled,
         boolean registered
     ) throws BinaryObjectException {
+        this(
+            ctx,
+            cls,
+            userType,
+            typeId,
+            typeName,
+            affKeyFieldName,
+            mapper,
+            serializer,
+            metaDataEnabled,
+            registered,
+            MarshallerExclusions.isExcluded(cls)
+        );
+    }
+
+    /**
+     * @param ctx Context.
+     * @param cls Class.
+     * @param userType User type flag.
+     * @param typeId Type ID.
+     * @param typeName Type name.
+     * @param affKeyFieldName Affinity key field name.
+     * @param mapper Mapper.
+     * @param serializer Serializer.
+     * @param metaDataEnabled Metadata enabled flag.
+     * @param registered Whether typeId has been successfully registered by MarshallerContext or not.
+     * @param excluded If true class values are explicitly excluded from marshalling, otherwise false.
+     * @throws BinaryObjectException In case of error.
+     */
+    BinaryClassDescriptor(
+        BinaryContext ctx,
+        Class<?> cls,
+        boolean userType,
+        int typeId,
+        String typeName,
+        @Nullable String affKeyFieldName,
+        @Nullable BinaryInternalMapper mapper,
+        @Nullable BinarySerializer serializer,
+        boolean metaDataEnabled,
+        boolean registered,
+        boolean excluded
+    ) throws BinaryObjectException {
         assert ctx != null;
         assert cls != null;
         assert mapper != null;
@@ -176,7 +218,7 @@ public class BinaryClassDescriptor {
 
         schemaReg = ctx.schemaRegistry(typeId);
 
-        excluded = MarshallerExclusions.isExcluded(cls);
+        this.excluded = excluded;
 
         if (excluded)
             mode = BinaryWriteMode.EXCLUSION;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index f9f7dd8..7077985 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -677,9 +677,25 @@ public class BinaryContext {
         Class cls;
 
         try {
-            cls = marshCtx.getClass(typeId, ldr);
+            if (GridBinaryMarshaller.USE_CACHE.get()) {
+                cls = marshCtx.getClass(typeId, ldr);
+
+                desc = descByCls.get(cls);
+            }
+            else {
+                String clsName = marshCtx.getClassName(JAVA_ID, typeId);
+
+                if (clsName == null)
+                    throw new ClassNotFoundException("Unknown type ID: " + typeId);
+
+                cls = U.forName(clsName, ldr, null);
+
+                desc = descByCls.get(cls);
+
+                if (desc == null)
+                    return createNoneCacheClassDescriptor(cls);
+            }
 
-            desc = descByCls.get(cls);
         }
         catch (ClassNotFoundException e) {
             // Class might have been loaded by default class loader.
@@ -707,6 +723,39 @@ public class BinaryContext {
     }
 
     /**
+     * Creates descriptor without registration.
+     *
+     * @param cls Class.
+     * @return Binary class descriptor.
+     */
+    @NotNull private BinaryClassDescriptor createNoneCacheClassDescriptor(Class cls) {
+        String clsName = cls.getName();
+
+        BinaryInternalMapper mapper = userTypeMapper(clsName);
+
+        int typeId = mapper.typeId(clsName);
+
+        String typeName = mapper.typeName(clsName);
+
+        BinarySerializer serializer = serializerForClass(cls);
+
+        String affFieldName = affinityFieldName(cls);
+
+        return new BinaryClassDescriptor(this,
+            cls,
+            true,
+            typeId,
+            typeName,
+            affFieldName,
+            mapper,
+            serializer,
+            true,
+            true,
+            false
+        );
+    }
+
+    /**
      * Attempts registration of the provided {@link BinaryClassDescriptor} in the cluster.
      *
      * @param desc Class descriptor to register.
@@ -724,11 +773,14 @@ public class BinaryContext {
         else {
             BinaryClassDescriptor regDesc = desc.makeRegistered();
 
-            BinaryClassDescriptor old = descByCls.putIfAbsent(desc.describedClass(), regDesc);
+            if (GridBinaryMarshaller.USE_CACHE.get()) {
+                BinaryClassDescriptor old = descByCls.putIfAbsent(desc.describedClass(), regDesc);
 
-            return old != null
-                ? old
-                : regDesc;
+                if (old != null)
+                    return old;
+            }
+
+            return regDesc;
         }
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java
index e6d80fd..5d5eb3e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java
@@ -174,10 +174,49 @@ public class BinaryEnumObjectImpl implements BinaryObjectEx, Externalizable, Cac
     }
 
     /** {@inheritDoc} */
-    @Override public <T> T deserialize() throws BinaryObjectException {
-        Class cls = BinaryUtils.resolveClass(ctx, typeId, clsName, ctx.configuration().getClassLoader(), false);
+    @Override public <T> T deserialize(@Nullable ClassLoader ldr) throws BinaryObjectException {
+        ClassLoader resolveLdr = ldr == null ? ctx.configuration().getClassLoader() : ldr;
+
+        if (ldr != null)
+            GridBinaryMarshaller.USE_CACHE.set(Boolean.FALSE);
+
+        try {
+            Class cls = BinaryUtils.resolveClass(ctx, typeId, clsName, resolveLdr, false);
 
-        return (T)BinaryEnumCache.get(cls, ord);
+            return (T)(ldr == null ? BinaryEnumCache.get(cls, ord) : uncachedValue(cls));
+        }
+        finally {
+            GridBinaryMarshaller.USE_CACHE.set(Boolean.TRUE);
+        }
+
+    }
+
+    /**
+     * Get value for the given class without any caching.
+     *
+     * @param cls Class.
+     */
+    private <T> T uncachedValue(Class<?> cls) throws BinaryObjectException {
+        assert cls != null;
+
+        assert !GridBinaryMarshaller.USE_CACHE.get();
+
+        if (ord >= 0) {
+            Object[] vals = cls.getEnumConstants();
+
+            if (ord < vals.length)
+                return (T)vals[ord];
+            else
+                throw new BinaryObjectException("Failed to get enum value for ordinal (do you have correct class " +
+                    "version?) [cls=" + cls.getName() + ", ordinal=" + ord + ", totalValues=" + vals.length + ']');
+        }
+        else
+            return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public <T> T deserialize() throws BinaryObjectException {
+        return (T)deserialize(null);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java
index 45e8b57..dfef1b9 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java
@@ -630,6 +630,21 @@ public final class BinaryObjectImpl extends BinaryObjectExImpl implements Extern
     }
 
     /** {@inheritDoc} */
+    @Nullable @Override public <T> T deserialize(@Nullable ClassLoader ldr) throws BinaryObjectException {
+        if (ldr == null)
+            return deserialize();
+
+        GridBinaryMarshaller.USE_CACHE.set(Boolean.FALSE);
+
+        try {
+            return (T)reader(null, ldr, true).deserialize();
+        }
+        finally {
+            GridBinaryMarshaller.USE_CACHE.set(Boolean.TRUE);
+        }
+    }
+
+    /** {@inheritDoc} */
     @Nullable @Override public <T> T deserialize() throws BinaryObjectException {
         Object obj0 = obj;
 
@@ -830,6 +845,7 @@ public final class BinaryObjectImpl extends BinaryObjectExImpl implements Extern
             BinaryHeapInputStream.create(arr, start),
             ldr,
             rCtx,
+            false,
             forUnmarshal);
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectOffheapImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectOffheapImpl.java
index 6f0176c..efe1c20 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectOffheapImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectOffheapImpl.java
@@ -415,6 +415,21 @@ public class BinaryObjectOffheapImpl extends BinaryObjectExImpl implements Exter
     }
 
     /** {@inheritDoc} */
+    @Nullable @Override public <T> T deserialize(@Nullable ClassLoader ldr) throws BinaryObjectException {
+        if (ldr == null)
+            return deserialize();
+
+        GridBinaryMarshaller.USE_CACHE.set(Boolean.FALSE);
+
+        try {
+            return (T)reader(null, ldr, true).deserialize();
+        }
+        finally {
+            GridBinaryMarshaller.USE_CACHE.set(Boolean.TRUE);
+        }
+    }
+
+    /** {@inheritDoc} */
     @Nullable @Override public <T> T deserialize() throws BinaryObjectException {
         return (T)deserializeValue();
     }
@@ -514,14 +529,28 @@ public class BinaryObjectOffheapImpl extends BinaryObjectExImpl implements Exter
      * @return Reader.
      */
     private BinaryReaderExImpl reader(@Nullable BinaryReaderHandles rCtx, boolean forUnmarshal) {
+        return reader(rCtx, ctx.configuration().getClassLoader(), forUnmarshal);
+    }
+
+    /**
+     * Create new reader for this object.
+     *
+     * @param rCtx Reader context.
+     * @param ldr Class loader.
+     * @param forUnmarshal {@code True} if reader is needed to unmarshal object.
+     * @return Reader.
+     */
+    private BinaryReaderExImpl reader(@Nullable BinaryReaderHandles rCtx, @Nullable ClassLoader ldr,
+        boolean forUnmarshal) {
         BinaryOffheapInputStream stream = new BinaryOffheapInputStream(ptr, size, false);
 
         stream.position(start);
 
         return new BinaryReaderExImpl(ctx,
             stream,
-            ctx.configuration().getClassLoader(),
+            ldr,
             rCtx,
+            false,
             forUnmarshal);
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
index 2f0ff23..6011217 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
@@ -1409,7 +1409,7 @@ public class BinaryReaderExImpl implements BinaryReader, BinaryRawReaderEx, Bina
             if (cls == null)
                 cls = cls0;
 
-            return BinaryUtils.doReadEnum(in, cls);
+            return BinaryUtils.doReadEnum(in, cls, GridBinaryMarshaller.USE_CACHE.get());
         }
         else
             return null;
@@ -1930,7 +1930,8 @@ public class BinaryReaderExImpl implements BinaryReader, BinaryRawReaderEx, Bina
                 break;
 
             case ENUM:
-                obj = BinaryUtils.doReadEnum(in, BinaryUtils.doReadClass(in, ctx, ldr));
+                obj = BinaryUtils.doReadEnum(in, BinaryUtils.doReadClass(in, ctx, ldr),
+                    GridBinaryMarshaller.USE_CACHE.get());
 
                 break;
 
@@ -1975,7 +1976,7 @@ public class BinaryReaderExImpl implements BinaryReader, BinaryRawReaderEx, Bina
         if (!findFieldById(fieldId))
             return null;
 
-        return new BinaryReaderExImpl(ctx, in, ldr, hnds, true).deserialize();
+        return new BinaryReaderExImpl(ctx, in, ldr, hnds, false, true).deserialize();
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
index 8491943..0b649b4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
@@ -1641,15 +1641,18 @@ public class BinaryUtils {
             cls = ctx.descriptorForTypeId(true, typeId, ldr, false).describedClass();
         else {
             String clsName = doReadClassName(in);
+            boolean useCache = GridBinaryMarshaller.USE_CACHE.get();
 
             try {
-                cls = U.forName(clsName, ldr);
+                cls = U.forName(clsName, ldr, null);
             }
             catch (ClassNotFoundException e) {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsName, e);
             }
 
-            ctx.registerClass(cls, false, false);
+            // forces registering of class by type id, at least locally
+            if (useCache)
+                ctx.registerClass(cls, false, false);
         }
 
         return cls;
@@ -1672,7 +1675,7 @@ public class BinaryUtils {
             cls = ctx.descriptorForTypeId(true, typeId, ldr, registerMeta).describedClass();
         else {
             try {
-                cls = U.forName(clsName, ldr);
+                cls = U.forName(clsName, ldr, null);
             }
             catch (ClassNotFoundException e) {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsName, e);
@@ -1736,9 +1739,10 @@ public class BinaryUtils {
      * Having target class in place we simply read ordinal and create final representation.
      *
      * @param cls Enum class.
+     * @param useCache True if class loader cache will be used, false otherwise.
      * @return Value.
      */
-    public static Enum<?> doReadEnum(BinaryInputStream in, Class<?> cls) throws BinaryObjectException {
+    public static Enum<?> doReadEnum(BinaryInputStream in, Class<?> cls, boolean useCache) throws BinaryObjectException {
         assert cls != null;
 
         if (!cls.isEnum())
@@ -1746,7 +1750,31 @@ public class BinaryUtils {
 
         int ord = in.readInt();
 
-        return BinaryEnumCache.get(cls, ord);
+        if (useCache)
+            return BinaryEnumCache.get(cls, ord);
+        else
+            return uncachedEnumValue(cls, ord);
+    }
+
+    /**
+     * Get value for the given class without any caching.
+     *
+     * @param cls Class.
+     */
+    private static <T> T uncachedEnumValue(Class<?> cls, int ord) throws BinaryObjectException {
+        assert cls != null;
+
+        if (ord >= 0) {
+            Object[] vals = cls.getEnumConstants();
+
+            if (ord < vals.length)
+                return (T)vals[ord];
+            else
+                throw new BinaryObjectException("Failed to get enum value for ordinal (do you have correct class " +
+                    "version?) [cls=" + cls.getName() + ", ordinal=" + ord + ", totalValues=" + vals.length + ']');
+        }
+        else
+            return null;
     }
 
     /**
@@ -1765,7 +1793,7 @@ public class BinaryUtils {
             if (flag == GridBinaryMarshaller.NULL)
                 arr[i] = null;
             else
-                arr[i] = doReadEnum(in, doReadClass(in, ctx, ldr));
+                arr[i] = doReadEnum(in, doReadClass(in, ctx, ldr), GridBinaryMarshaller.USE_CACHE.get());
         }
 
         return arr;
@@ -1798,7 +1826,7 @@ public class BinaryUtils {
      */
     @Nullable public static Object doReadObject(BinaryInputStream in, BinaryContext ctx, ClassLoader ldr,
         BinaryReaderHandlesHolder handles) throws BinaryObjectException {
-        return new BinaryReaderExImpl(ctx, in, ldr, handles.handles(), true).deserialize();
+        return new BinaryReaderExImpl(ctx, in, ldr, handles.handles(), false, true).deserialize();
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/GridBinaryMarshaller.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/GridBinaryMarshaller.java
index b59dd20..b31408e5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/GridBinaryMarshaller.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/GridBinaryMarshaller.java
@@ -36,6 +36,9 @@ public class GridBinaryMarshaller {
     private static final ThreadLocal<BinaryContextHolder> BINARY_CTX =
         ThreadLocal.withInitial(BinaryContextHolder::new);
 
+    /** Flag whether class caching should be used by the current thread. */
+    public static final ThreadLocal<Boolean> USE_CACHE = ThreadLocal.withInitial(() -> Boolean.TRUE);
+
     /** */
     public static final byte OPTM_MARSH = -2;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java
index 83e8dcc..42425c5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java
@@ -47,6 +47,7 @@ import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentMap;
 
+import org.apache.ignite.internal.binary.GridBinaryMarshaller;
 import org.apache.ignite.internal.util.GridUnsafe;
 import org.apache.ignite.internal.util.SerializableTransient;
 import org.apache.ignite.internal.util.TransientSerializable;
@@ -196,6 +197,34 @@ class OptimizedClassDescriptor {
         MarshallerContext ctx,
         OptimizedMarshallerIdMapper mapper)
         throws IOException {
+        this(
+            cls,
+            typeId,
+            clsMap,
+            ctx,
+            mapper,
+            MarshallerExclusions.isExcluded(cls)
+        );
+    }
+
+    /**
+     * Creates descriptor for class.
+     *
+     * @param typeId Type ID.
+     * @param clsMap Class descriptors by class map.
+     * @param cls Class.
+     * @param ctx Context.
+     * @param mapper ID mapper.
+     * @throws IOException In case of error.
+     */
+    @SuppressWarnings("ForLoopReplaceableByForEach")
+    OptimizedClassDescriptor(Class<?> cls,
+        int typeId,
+        ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
+        MarshallerContext ctx,
+        OptimizedMarshallerIdMapper mapper,
+        boolean excluded)
+        throws IOException {
         this.cls = cls;
         this.typeId = typeId;
         this.clsMap = clsMap;
@@ -204,7 +233,7 @@ class OptimizedClassDescriptor {
 
         name = cls.getName();
 
-        excluded = MarshallerExclusions.isExcluded(cls);
+        this.excluded = excluded;
 
         if (!excluded) {
             Class<?> parent;
@@ -741,6 +770,7 @@ class OptimizedClassDescriptor {
             case OBJ_ARR:
                 OptimizedClassDescriptor compDesc = OptimizedMarshallerUtils.classDescriptor(clsMap,
                     obj.getClass().getComponentType(),
+                    GridBinaryMarshaller.USE_CACHE.get(),
                     ctx,
                     mapper);
 
@@ -801,7 +831,8 @@ class OptimizedClassDescriptor {
                 break;
 
             case CLS:
-                OptimizedClassDescriptor clsDesc = OptimizedMarshallerUtils.classDescriptor(clsMap, (Class<?>)obj, ctx, mapper);
+                OptimizedClassDescriptor clsDesc = OptimizedMarshallerUtils.classDescriptor(
+                    clsMap, (Class<?>)obj, GridBinaryMarshaller.USE_CACHE.get(), ctx, mapper);
 
                 clsDesc.writeTypeData(out);
 
@@ -811,7 +842,8 @@ class OptimizedClassDescriptor {
                 out.writeInt(proxyIntfs.length);
 
                 for (Class<?> intf : proxyIntfs) {
-                    OptimizedClassDescriptor intfDesc = OptimizedMarshallerUtils.classDescriptor(clsMap, intf, ctx, mapper);
+                    OptimizedClassDescriptor intfDesc = OptimizedMarshallerUtils.classDescriptor(
+                        clsMap, intf, GridBinaryMarshaller.USE_CACHE.get(), ctx, mapper);
 
                     intfDesc.writeTypeData(out);
                 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshaller.java b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshaller.java
index f5ae585..f5fab80 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshaller.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshaller.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentMap;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.internal.binary.GridBinaryMarshaller;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.marshaller.AbstractNodeNameAwareMarshaller;
@@ -103,6 +104,9 @@ public class OptimizedMarshaller extends AbstractNodeNameAwareMarshaller {
     /** */
     private OptimizedObjectStreamRegistry registry = new OptimizedObjectSharedStreamRegistry();
 
+    /** Non cached registry. */
+    private OptimizedObjectSharedStreamRegistry nonCachedRegistry = new OptimizedObjectSharedStreamRegistry();
+
     /**
      * Creates new marshaller will all defaults.
      *
@@ -218,14 +222,29 @@ public class OptimizedMarshaller extends AbstractNodeNameAwareMarshaller {
 
     /** {@inheritDoc} */
     @Override protected <T> T unmarshal0(InputStream in, @Nullable ClassLoader clsLdr) throws IgniteCheckedException {
+        return unmarshal0(in, clsLdr, GridBinaryMarshaller.USE_CACHE.get());
+    }
+
+    /**
+     * Unmarshals object from the input stream using given class loader.
+     * This method should not close given input stream.
+     *
+     * @param <T> Type of unmarshalled object.
+     * @param in Input stream.
+     * @param clsLdr Class loader to use.
+     * @param useCache True if class loader cache will be used, false otherwise.
+     * @return Unmarshalled object.
+     * @throws IgniteCheckedException If unmarshalling failed.
+     */
+    protected <T> T unmarshal0(InputStream in, @Nullable ClassLoader clsLdr, boolean useCache) throws IgniteCheckedException {
         assert in != null;
 
         OptimizedObjectInputStream objIn = null;
 
         try {
-            objIn = registry.in();
+            objIn = !useCache ? nonCachedRegistry.in() : registry.in();
 
-            objIn.context(clsMap, ctx, mapper, clsLdr != null ? clsLdr : dfltClsLdr);
+            objIn.context(clsMap, ctx, mapper, clsLdr != null ? clsLdr : dfltClsLdr, useCache);
 
             objIn.in().inputStream(in);
 
@@ -242,7 +261,10 @@ public class OptimizedMarshaller extends AbstractNodeNameAwareMarshaller {
                 "[clsLdr=" + clsLdr + ", err=" + e.getMessage() + "]", e);
         }
         finally {
-            registry.closeIn(objIn);
+            if (!useCache)
+                nonCachedRegistry.closeNotCachedIn(objIn);
+            else
+                registry.closeIn(objIn);
         }
     }
 
@@ -255,7 +277,8 @@ public class OptimizedMarshaller extends AbstractNodeNameAwareMarshaller {
         try {
             objIn = registry.in();
 
-            objIn.context(clsMap, ctx, mapper, clsLdr != null ? clsLdr : dfltClsLdr);
+            objIn.context(clsMap, ctx, mapper,
+                clsLdr != null ? clsLdr : dfltClsLdr, GridBinaryMarshaller.USE_CACHE.get());
 
             objIn.in().bytes(arr, arr.length);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshallerUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshallerUtils.java
index 3ede240..4ffd1e8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshallerUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedMarshallerUtils.java
@@ -32,7 +32,9 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.util.GridUnsafe;
 import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.marshaller.MarshallerContext;
+import org.jetbrains.annotations.NotNull;
 
 import static org.apache.ignite.internal.MarshallerPlatformIds.JAVA_ID;
 
@@ -181,6 +183,7 @@ class OptimizedMarshallerUtils {
      *
      * @param clsMap Class descriptors by class map.
      * @param cls Class.
+     * @param useCache True if class loader cache will be used, false otherwise.
      * @param ctx Context.
      * @param mapper ID mapper.
      * @return Descriptor.
@@ -189,13 +192,18 @@ class OptimizedMarshallerUtils {
     static OptimizedClassDescriptor classDescriptor(
         ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
         Class cls,
+        boolean useCache,
         MarshallerContext ctx,
         OptimizedMarshallerIdMapper mapper)
         throws IOException
     {
         OptimizedClassDescriptor desc = clsMap.get(cls);
 
-        if (desc == null) {
+        if (desc == null && !useCache) {
+            desc = new OptimizedClassDescriptor(cls, resolveTypeId(cls.getName(),
+                mapper), clsMap, ctx, mapper, false);
+        }
+        else if (desc == null) {
             int typeId = resolveTypeId(cls.getName(), mapper);
 
             boolean registered;
@@ -246,6 +254,7 @@ class OptimizedMarshallerUtils {
      * @param clsMap Class descriptors by class map.
      * @param typeId ID.
      * @param ldr Class loader.
+     * @param useCache True if class loader cache will be used, false otherwise.
      * @param ctx Context.
      * @param mapper ID mapper.
      * @return Descriptor.
@@ -256,8 +265,64 @@ class OptimizedMarshallerUtils {
         ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
         int typeId,
         ClassLoader ldr,
+        boolean useCache,
         MarshallerContext ctx,
         OptimizedMarshallerIdMapper mapper) throws IOException, ClassNotFoundException {
+        OptimizedClassDescriptor desc;
+
+        if (useCache)
+            desc = descriptorFromCache(clsMap, typeId, ldr, ctx, mapper);
+        else
+            desc = descriptorWithoutCache(clsMap, typeId, ldr, ctx, mapper);
+
+        return desc;
+    }
+
+    /**
+     * @param clsMap Class descriptors by class map.
+     * @param typeId Type id.
+     * @param ldr Loader.
+     * @param ctx Context.
+     * @param mapper Mapper.
+     */
+    @NotNull
+    private static OptimizedClassDescriptor descriptorWithoutCache(ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
+        int typeId, ClassLoader ldr, MarshallerContext ctx,
+        OptimizedMarshallerIdMapper mapper) throws ClassNotFoundException, IOException {
+        String clsName;
+
+        try {
+            clsName = ctx.getClassName(JAVA_ID, typeId);
+
+            if (clsName == null)
+                throw new ClassNotFoundException("Unknown type ID: " + typeId);
+        }
+        catch (IgniteCheckedException e) {
+            throw new IOException("Failed to resolve class for ID: " + typeId, e);
+        }
+
+        Class cls = U.forName(clsName, ldr, null);
+
+        OptimizedClassDescriptor desc = clsMap.get(cls);
+
+        if (desc == null)
+            desc = new OptimizedClassDescriptor(cls, resolveTypeId(cls.getName(),
+                mapper), clsMap, ctx, mapper, false);
+
+        return desc;
+    }
+
+    /**
+     * @param clsMap Class descriptors by class map.
+     * @param typeId Type id.
+     * @param ldr Loader.
+     * @param ctx Context.
+     * @param mapper Mapper.
+     */
+    @NotNull
+    private static OptimizedClassDescriptor descriptorFromCache(ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
+        int typeId, ClassLoader ldr, MarshallerContext ctx,
+        OptimizedMarshallerIdMapper mapper) throws ClassNotFoundException, IOException {
         Class cls;
 
         try {
@@ -276,7 +341,6 @@ class OptimizedMarshallerUtils {
             if (old != null)
                 desc = old;
         }
-
         return desc;
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectInputStream.java b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectInputStream.java
index cbc8c31..579d765 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectInputStream.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectInputStream.java
@@ -129,12 +129,16 @@ class OptimizedObjectInputStream extends ObjectInputStream {
     /** */
     private ConcurrentMap<Class, OptimizedClassDescriptor> clsMap;
 
+    /** The flag shown, the reader uses class loader cache used or not. */
+    private boolean useCache;
+
     /**
      * @param in Input.
      * @throws IOException In case of error.
      */
     OptimizedObjectInputStream(GridDataInput in) throws IOException {
         this.in = in;
+        this.useCache = true;
     }
 
     /**
@@ -142,17 +146,20 @@ class OptimizedObjectInputStream extends ObjectInputStream {
      * @param ctx Context.
      * @param mapper ID mapper.
      * @param clsLdr Class loader.
+     * @param useCache True if class loader cache will be used, false otherwise.
      */
     void context(
         ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
         MarshallerContext ctx,
         OptimizedMarshallerIdMapper mapper,
-        ClassLoader clsLdr)
+        ClassLoader clsLdr,
+        boolean useCache)
     {
         this.clsMap = clsMap;
         this.ctx = ctx;
         this.mapper = mapper;
         this.clsLdr = clsLdr;
+        this.useCache = useCache;
     }
 
     /**
@@ -337,8 +344,8 @@ class OptimizedObjectInputStream extends ObjectInputStream {
                 int typeId = readInt();
 
                 OptimizedClassDescriptor desc = typeId == 0 ?
-                    classDescriptor(clsMap, U.forName(readUTF(), clsLdr, ctx.classNameFilter()), ctx, mapper) :
-                    classDescriptor(clsMap, typeId, clsLdr, ctx, mapper);
+                    classDescriptor(clsMap, U.forName(readUTF(), clsLdr, ctx.classNameFilter()), useCache, ctx, mapper) :
+                    classDescriptor(clsMap, typeId, clsLdr, useCache, ctx, mapper);
 
                 curCls = desc.describedClass();
 
@@ -373,8 +380,8 @@ class OptimizedObjectInputStream extends ObjectInputStream {
     private Class<?> readClass() throws ClassNotFoundException, IOException {
         int compTypeId = readInt();
 
-        return compTypeId == 0 ? U.forName(readUTF(), clsLdr) :
-            classDescriptor(clsMap, compTypeId, clsLdr, ctx, mapper).describedClass();
+        return compTypeId == 0 ? U.forName(readUTF(), clsLdr, null, useCache) :
+            classDescriptor(clsMap, compTypeId, clsLdr, useCache, ctx, mapper).describedClass();
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectOutputStream.java b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectOutputStream.java
index 7c8e5af..2deddec 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectOutputStream.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectOutputStream.java
@@ -39,6 +39,7 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentMap;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.binary.GridBinaryMarshaller;
 import org.apache.ignite.internal.util.GridHandleTable;
 import org.apache.ignite.internal.util.io.GridDataOutput;
 import org.apache.ignite.internal.util.typedef.F;
@@ -201,6 +202,7 @@ public class OptimizedObjectOutputStream extends ObjectOutputStream {
                 OptimizedClassDescriptor desc = classDescriptor(
                     clsMap,
                     obj instanceof Object[] ? Object[].class : obj.getClass(),
+                    GridBinaryMarshaller.USE_CACHE.get(),
                     ctx,
                     mapper);
 
@@ -228,6 +230,7 @@ public class OptimizedObjectOutputStream extends ObjectOutputStream {
 
                     desc = classDescriptor(clsMap,
                         obj instanceof Object[] ? Object[].class : obj.getClass(),
+                        GridBinaryMarshaller.USE_CACHE.get(),
                         ctx,
                         mapper);
                 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectSharedStreamRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectSharedStreamRegistry.java
index d430315..c938ccb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectSharedStreamRegistry.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectSharedStreamRegistry.java
@@ -57,6 +57,23 @@ public class OptimizedObjectSharedStreamRegistry extends OptimizedObjectStreamRe
     }
 
     /**
+     * Closes and releases not cached input stream.
+     *
+     * @param in Object input stream.
+     */
+    void closeNotCachedIn(OptimizedObjectInputStream in) {
+        U.close(in, null);
+
+        StreamHolder holder = holders.get();
+
+        if (holder != null) {
+            holder.releaseIn();
+
+            holders.set(null);
+        }
+    }
+
+    /**
      * Gets holder from pool or thread local.
      *
      * @return Stream holder.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
index a8e4f5f..38a701d 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
@@ -203,6 +203,7 @@ import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.IgniteInterruptedCheckedException;
 import org.apache.ignite.internal.IgniteNodeAttributes;
+import org.apache.ignite.internal.binary.GridBinaryMarshaller;
 import org.apache.ignite.internal.cluster.ClusterGroupEmptyCheckedException;
 import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
 import org.apache.ignite.internal.compute.ComputeTaskCancelledCheckedException;
@@ -8867,7 +8868,7 @@ public abstract class IgniteUtils {
      * @throws ClassNotFoundException If class not found.
      */
     public static Class<?> forName(String clsName, @Nullable ClassLoader ldr) throws ClassNotFoundException {
-        return U.forName(clsName, ldr, null);
+        return U.forName(clsName, ldr, null, GridBinaryMarshaller.USE_CACHE.get());
     }
 
     /**
@@ -8879,6 +8880,19 @@ public abstract class IgniteUtils {
      * @throws ClassNotFoundException If class not found.
      */
     public static Class<?> forName(String clsName, @Nullable ClassLoader ldr, IgnitePredicate<String> clsFilter) throws ClassNotFoundException {
+        return forName(clsName, ldr, clsFilter, GridBinaryMarshaller.USE_CACHE.get());
+    }
+
+    /**
+     * Gets class for provided name. Accepts primitive types names.
+     *
+     * @param clsName Class name.
+     * @param ldr Class loader.
+    * @param useCache If true class loader and result should be cached internally, false otherwise.
+     * @return Class.
+     * @throws ClassNotFoundException If class not found.
+     */
+    public static Class<?> forName(String clsName, @Nullable ClassLoader ldr, IgnitePredicate<String> clsFilter, boolean useCache) throws ClassNotFoundException {
         assert clsName != null;
 
         Class<?> cls = primitiveMap.get(clsName);
@@ -8889,10 +8903,21 @@ public abstract class IgniteUtils {
         if (ldr != null) {
             if (ldr instanceof ClassCache)
                 return ((ClassCache)ldr).getFromCache(clsName);
+            else if (!useCache) {
+                cls = Class.forName(clsName, true, ldr);
+
+                return cls;
+            }
         }
         else
             ldr = gridClassLoader;
 
+        if (!useCache) {
+            cls = Class.forName(clsName, true, ldr);
+
+            return cls;
+        }
+
         ConcurrentMap<String, Class> ldrMap = classCache.get(ldr);
 
         if (ldrMap == null) {
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java
new file mode 100644
index 0000000..ff4b60c
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.binary;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.URL;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CachePeekMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteInterruptedCheckedException;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.lang.IgniteRunnable;
+import org.apache.ignite.testframework.GridTestExternalClassLoader;
+import org.apache.ignite.testframework.config.GridTestProperties;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+/**
+ * Test class covering feature that allows to unmarshal binary object with custom classloader.
+ */
+public class BinaryClassLoaderMultiJvmTest extends GridCommonAbstractTest {
+    /** Person class name. */
+    private static final String PERSON_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Person";
+
+    /** Enum class name. */
+    private static final String ENUM_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Color";
+
+    /** Organization class name. */
+    private static final String ORGANIZATION_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Organization";
+
+    /** Address class name. */
+    private static final String ADDRESS_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Address";
+
+    /** Enum values. */
+    private static final String[] enumVals = {"GREY", "RED", "GREEN", "PURPLE", "LIGHTBLUE"};
+
+    /** Client id. */
+    public static final int CLIENT_ID = 1;
+
+    /** {@inheritDoc} */
+    @Override protected boolean isMultiJvm() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setNetworkTimeout(10000)
+            .setClientMode(getTestIgniteInstanceName(CLIENT_ID).equals(igniteInstanceName))
+            .setCacheConfiguration(
+                new CacheConfiguration("SomeCache")
+                    .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+                    .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC),
+                new CacheConfiguration("SomeCacheEnum")
+                    .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+                    .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC),
+                new CacheConfiguration("OrganizationCache")
+                    .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+                    .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC));
+    }
+
+    /**
+     * Checks that binary object deserialization works with custom classloader.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testLoadClassFromBinary() throws Exception {
+        ClassLoader testClsLdr = new GridTestExternalClassLoader(new URL[] {
+            new URL(GridTestProperties.getProperty("p2p.uri.cls"))});
+
+        WeakReference<ClassLoader> clsLdrRef = new WeakReference<>(testClsLdr);
+
+        try {
+            Ignite ign = startGrids(2);
+
+            ign.compute(ign.cluster().forRemotes()).broadcast(new IgniteRunnable() {
+                @Override public void run() {
+
+                    Ignite client = Ignition.ignite(getTestIgniteInstanceName(CLIENT_ID));
+
+                    ClassLoader clientCestClsLdr;
+
+                    try {
+                        clientCestClsLdr = new GridTestExternalClassLoader(new URL[] {
+                            new URL(GridTestProperties.getProperty("p2p.uri.cls"))});
+
+                        info("Client name: " + client.name());
+
+                        loadItems(clientCestClsLdr, client);
+                        loadOrganization(clientCestClsLdr, client);
+                        loadEnumItems(clientCestClsLdr, client);
+                    }
+                    catch (Exception e) {
+                        e.printStackTrace();
+                    }
+
+                }
+            });
+
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ign);
+
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ign);
+
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ign);
+
+            testClsLdr = null;
+
+            checkClassCacheEmpty(clsLdrRef);
+        }
+        finally {
+            stopAllGrids();
+        }
+    }
+
+    /**
+     * Checks that binary object deserialization works with custom classloader if called on client side.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testClientLoadClassFromBinary() throws Exception {
+        ClassLoader testClsLdr = new GridTestExternalClassLoader(new URL[] {
+            new URL(GridTestProperties.getProperty("p2p.uri.cls"))});
+
+        WeakReference<ClassLoader> clsLdrRef = new WeakReference<>(testClsLdr);
+
+        try {
+            Ignite ign = startGrids(2);
+
+            ign.compute(ign.cluster().forClients()).broadcast(new IgniteRunnable() {
+                @Override public void run() {
+
+                    Ignite client = Ignition.ignite(getTestIgniteInstanceName(CLIENT_ID));
+
+                    ClassLoader clientCestClsLdr;
+
+                    try {
+                        clientCestClsLdr = new GridTestExternalClassLoader(new URL[] {
+                            new URL(GridTestProperties.getProperty("p2p.uri.cls"))});
+
+                        info("Client name: " + client.name());
+
+                        loadItems(clientCestClsLdr, client);
+                        loadOrganization(clientCestClsLdr, client);
+                        loadEnumItems(clientCestClsLdr, client);
+                    }
+                    catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            });
+
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ign);
+
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ign);
+
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ign);
+
+            testClsLdr = null;
+
+            checkClassCacheEmpty(clsLdrRef);
+        }
+        finally {
+            stopAllGrids();
+        }
+    }
+
+    /**
+     * @param clsLdrRef Weak reference to testclassLoader.
+     */
+    private void checkClassCacheEmpty(WeakReference<ClassLoader> clsLdrRef) throws IgniteInterruptedCheckedException {
+        System.gc();
+        System.runFinalization();
+
+        IgniteUtils.sleep(1_000);
+
+        assertNull(clsLdrRef.get());
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsName, Ignite ignite) {
+        IgniteCache<Integer, Object> cache = ignite.cache(cacheName);
+
+        IgniteCache<Integer, BinaryObject> binaryCache = cache.withKeepBinary();
+
+        for (int i = 0; i < 100; i++) {
+
+            BinaryObject binaryVal = binaryCache.get(i);
+
+            if (i % 50 == 0)
+                try {
+                    info("Val: " + binaryVal.toString());
+                }
+                catch (IgniteException e) {
+                    info("Can not execute toString() on class " + binaryVal.type().typeName());
+                }
+
+            assertEquals(binaryVal.type().typeName(), valClsName);
+
+            boolean catchEx = false;
+
+            try {
+                binaryVal.deserialize();
+            }
+            catch (BinaryObjectException e) {
+                ClassNotFoundException cause = X.cause(e, ClassNotFoundException.class);
+
+                if (cause != null && cause.getMessage().contains(valClsName))
+                    catchEx = true;
+                else
+                    throw e;
+            }
+
+            assertTrue(catchEx);
+
+            Object personVal = binaryVal.deserialize(testClsLdr);
+
+            assertTrue(personVal != null && personVal.getClass().getName().equals(valClsName));
+        }
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void loadItems(ClassLoader testClsLdr, Ignite ignite) throws Exception {
+        Constructor personConstructor = testClsLdr.loadClass(PERSON_CLASS_NAME).getConstructor(String.class);
+
+        IgniteCache<Integer, Object> cache = ignite.cache("SomeCache");
+
+        for (int i = 0; i < 100; i++)
+            cache.put(i, personConstructor.newInstance("Persone name " + i));
+
+        assertEquals(cache.size(CachePeekMode.PRIMARY), 100);
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void loadOrganization(ClassLoader testClsLdr, Ignite ignite) throws Exception {
+        Class personCls = testClsLdr.loadClass(PERSON_CLASS_NAME);
+        Class addrCls = testClsLdr.loadClass(ADDRESS_CLASS_NAME);
+
+        Constructor personConstructor = testClsLdr.loadClass(PERSON_CLASS_NAME).getConstructor(String.class);
+        Constructor addrConstructor = testClsLdr.loadClass(ADDRESS_CLASS_NAME).getConstructor(String.class, Integer.TYPE);
+        Constructor organizationConstructor = testClsLdr.loadClass(ORGANIZATION_CLASS_NAME).getConstructor(String.class, personCls, addrCls);
+
+        IgniteCache<Integer, Object> cache = ignite.cache("OrganizationCache");
+
+        for (int i = 0; i < 100; i++)
+            cache.put(i, organizationConstructor.newInstance("Organization " + i,
+                personConstructor.newInstance("Persone name " + i),
+                addrConstructor.newInstance("Street " + i, i)));
+
+        assertEquals(cache.size(CachePeekMode.PRIMARY), 100);
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void loadEnumItems(ClassLoader testClsLdr, Ignite ignite) throws Exception {
+        Method factoryMtd = testClsLdr.loadClass(ENUM_CLASS_NAME).getMethod("valueOf", String.class);
+
+        IgniteCache<Integer, Object> cache = ignite.cache("SomeCacheEnum");
+
+        for (int i = 0; i < 100; i++)
+            cache.put(i, factoryMtd.invoke(null, enumVals[i % enumVals.length]));
+
+        assertEquals(cache.size(CachePeekMode.PRIMARY), 100);
+    }
+
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderTest.java b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderTest.java
new file mode 100644
index 0000000..b3b0d54
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.binary;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.URL;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CachePeekMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.testframework.GridTestExternalClassLoader;
+import org.apache.ignite.testframework.config.GridTestProperties;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+/**
+ * Test class covering feature that allows to unmarshal binary object with custom classloader.
+ */
+public class BinaryClassLoaderTest extends GridCommonAbstractTest {
+    /** Person class name. */
+    private static final String PERSON_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Person";
+
+    /** Enum class name. */
+    private static final String ENUM_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Color";
+
+    /** Organization class name. */
+    private static final String ORGANIZATION_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Organization";
+
+    /** Address class name. */
+    private static final String ADDRESS_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Address";
+
+    /** Enum vals. */
+    private static final String[] enumVals = {"GREY", "RED", "GREEN", "PURPLE", "LIGHTBLUE"};
+
+    /** Start client flag. */
+    private boolean startClient;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setNetworkTimeout(10000)
+            .setClientMode(startClient)
+            .setCacheConfiguration(
+                new CacheConfiguration("SomeCache")
+                    .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+                    .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC),
+                new CacheConfiguration("SomeCacheEnum")
+                    .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+                    .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC),
+                new CacheConfiguration("OrganizationCache")
+                    .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+                    .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC));
+    }
+
+    /**
+     * Checks that binary object deserialization works with custom classloader.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testLoadClassFromBinary() throws Exception {
+        ClassLoader testClsLdr = new GridTestExternalClassLoader(new URL[]{
+            new URL(GridTestProperties.getProperty("p2p.uri.cls"))});
+
+        try {
+            final Ignite ignite1 = startGrid(1);
+            final Ignite ignite2 = startGrid(2);
+            final Ignite ignite3 = startGrid(3);
+
+            loadItems(testClsLdr, ignite1);
+            loadOrganization(testClsLdr, ignite1);
+            loadEnumItems(testClsLdr, ignite1);
+
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ignite1);
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ignite2);
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ignite3);
+
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ignite1);
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ignite2);
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ignite3);
+
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ignite1);
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ignite2);
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ignite3);
+        }
+        finally {
+            stopAllGrids();
+        }
+    }
+
+    /**
+     * Checks that binary object deserialization works with custom classloader if called on client side.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testClientLoadClassFromBinary() throws Exception {
+        ClassLoader testClsLdr = new GridTestExternalClassLoader(new URL[]{
+            new URL(GridTestProperties.getProperty("p2p.uri.cls"))});
+
+        try {
+            final Ignite ignite1 = startGrid(1);
+            final Ignite ignite2 = startGrid(2);
+
+            startClient = true;
+
+            final Ignite client = startGrid(3);
+
+            loadItems(testClsLdr, client);
+            loadOrganization(testClsLdr, client);
+            loadEnumItems(testClsLdr, client);
+
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ignite1);
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ignite2);
+            checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, client);
+
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ignite1);
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ignite2);
+            checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, client);
+
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ignite1);
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ignite2);
+            checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, client);
+        }
+        finally {
+            stopAllGrids();
+        }
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsName, Ignite ignite) {
+        IgniteCache<Integer, Object> cache = ignite.cache(cacheName);
+
+        IgniteCache<Integer, BinaryObject> binaryCache = cache.withKeepBinary();
+
+        for (int i = 0; i < 100; i++) {
+
+            BinaryObject binaryVal = binaryCache.get(i);
+
+            if (i % 50 == 0)
+                try {
+                    info("Val: " + binaryVal.toString());
+                } catch (IgniteException e) {
+                    info("Can not execute toString() on class " + binaryVal.type().typeName());
+                }
+
+            assertEquals(binaryVal.type().typeName(), valClsName);
+
+            boolean catchEx = false;
+
+            try {
+                binaryVal.deserialize();
+            } catch (BinaryObjectException e) {
+                ClassNotFoundException cause = X.cause(e, ClassNotFoundException.class);
+
+                if (cause != null && cause.getMessage().contains(valClsName))
+                    catchEx = true;
+                else
+                    throw e;
+            }
+
+            assertTrue(catchEx);
+
+            Object personVal = binaryVal.deserialize(testClsLdr);
+
+            assertTrue(personVal != null && personVal.getClass().getName().equals(valClsName));
+        }
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void loadItems(ClassLoader testClsLdr, Ignite ignite) throws Exception {
+        Constructor personConstructor = testClsLdr.loadClass(PERSON_CLASS_NAME).getConstructor(String.class);
+
+        IgniteCache<Integer, Object> cache = ignite.cache("SomeCache");
+
+        for (int i = 0; i < 100; i++)
+            cache.put(i, personConstructor.newInstance("Persone name " + i));
+
+        assertEquals(cache.size(CachePeekMode.PRIMARY), 100);
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void loadOrganization(ClassLoader testClsLdr, Ignite ignite) throws Exception {
+        Class personCls = testClsLdr.loadClass(PERSON_CLASS_NAME);
+        Class addrCls = testClsLdr.loadClass(ADDRESS_CLASS_NAME);
+
+        Constructor personConstructor = testClsLdr.loadClass(PERSON_CLASS_NAME).getConstructor(String.class);
+        Constructor addrConstructor = testClsLdr.loadClass(ADDRESS_CLASS_NAME).getConstructor(String.class, Integer.TYPE);
+        Constructor organizationConstructor = testClsLdr.loadClass(ORGANIZATION_CLASS_NAME).getConstructor(String.class, personCls, addrCls);
+
+        IgniteCache<Integer, Object> cache = ignite.cache("OrganizationCache");
+
+        for (int i = 0; i < 100; i++)
+            cache.put(i, organizationConstructor.newInstance("Organization " + i,
+                personConstructor.newInstance("Persone name " + i),
+                addrConstructor.newInstance("Street " + i, i)));
+
+        assertEquals(cache.size(CachePeekMode.PRIMARY), 100);
+    }
+
+    /**
+     * @param testClsLdr Test class loader.
+     * @param ignite Ignite.
+     */
+    private void loadEnumItems(ClassLoader testClsLdr, Ignite ignite) throws Exception {
+        Method factoryMtd = testClsLdr.loadClass(ENUM_CLASS_NAME).getMethod("valueOf", String.class);
+
+        IgniteCache<Integer, Object> cache = ignite.cache("SomeCacheEnum");
+
+        for (int i = 0; i < 100; i++)
+            cache.put(i, factoryMtd.invoke(null, enumVals[i % enumVals.length]));
+
+        assertEquals(cache.size(CachePeekMode.PRIMARY), 100);
+    }
+
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectStreamSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectStreamSelfTest.java
index 9a2c453..d374ddb 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectStreamSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/marshaller/optimized/OptimizedObjectStreamSelfTest.java
@@ -1192,7 +1192,7 @@ public class OptimizedObjectStreamSelfTest extends GridCommonAbstractTest {
 
             in = reg.in();
 
-            in.context(clsMap, CTX, null, getClass().getClassLoader());
+            in.context(clsMap, CTX, null, getClass().getClassLoader(), true);
 
             in.in().bytes(arr, arr.length);
 
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsCacheTestSuite3.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsCacheTestSuite3.java
index e6a0a50..2026bd4 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsCacheTestSuite3.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsCacheTestSuite3.java
@@ -20,6 +20,8 @@ package org.apache.ignite.testsuites;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import org.apache.ignite.internal.binary.BinaryClassLoaderMultiJvmTest;
+import org.apache.ignite.internal.binary.BinaryClassLoaderTest;
 import org.apache.ignite.internal.processors.cache.binary.GridCacheBinaryAtomicEntryProcessorDeploymentSelfTest;
 import org.apache.ignite.internal.processors.cache.binary.GridCacheBinaryTransactionalEntryProcessorDeploymentSelfTest;
 import org.apache.ignite.testframework.GridTestUtils;
@@ -64,6 +66,8 @@ public class IgniteBinaryObjectsCacheTestSuite3 {
 
         GridTestUtils.addTestIfNeeded(suite, GridCacheBinaryAtomicEntryProcessorDeploymentSelfTest.class, ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, GridCacheBinaryTransactionalEntryProcessorDeploymentSelfTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, BinaryClassLoaderTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, BinaryClassLoaderMultiJvmTest.class, ignoredTests);
 
         return suite;
     }
diff --git a/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Address.java b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Address.java
new file mode 100644
index 0000000..ccd14cb
--- /dev/null
+++ b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Address.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tests.p2p.cache;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+/**
+ */
+public class Address implements Externalizable {
+    private String street;
+
+    private int house;
+
+    public Address() {
+    }
+
+    public Address(String street, int house) {
+        this.street = street;
+        this.house = house;
+    }
+
+    public String getStreet() {
+        return street;
+    }
+
+    public int getHouse() {
+        return house;
+    }
+
+    @Override public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(street);
+        out.writeInt(house);
+    }
+
+    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        street = (String)in.readObject();
+        house = in.readInt();
+    }
+}
diff --git a/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Color.java b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Color.java
new file mode 100644
index 0000000..6d74b6c
--- /dev/null
+++ b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Color.java
@@ -0,0 +1,50 @@
+/*
+ * 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.tests.p2p.cache;
+
+import java.io.Serializable;
+
+/**
+ * Car color
+ */
+public enum Color implements Serializable {
+    /**
+     * Grey color.
+     */
+    GREY,
+
+    /**
+     * Red color.
+     */
+    RED,
+
+    /**
+     * Green color.
+     */
+    GREEN,
+
+    /**
+     * Purple color.
+     */
+    PURPLE,
+
+    /**
+     * Light blue color.
+     */
+    LIGHTBLUE
+}
diff --git a/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Organization.java b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Organization.java
new file mode 100644
index 0000000..90c2c7a
--- /dev/null
+++ b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Organization.java
@@ -0,0 +1,49 @@
+/*
+ * 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.tests.p2p.cache;
+
+/**
+ */
+public class Organization {
+    /** Title. */
+    String title;
+
+    /** Head. */
+    Person head;
+
+    /** Address. */
+    Address addr;
+
+    public Organization(String title, Person head, Address addr) {
+        this.title = title;
+        this.head = head;
+        this.addr = addr;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public Person getHead() {
+        return head;
+    }
+
+    public Address getAddr() {
+        return addr;
+    }
+}


Mime
View raw message