This is an automated email from the ASF dual-hosted git repository.

shishkovilja 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 a20d1c0f302 IGNITE-28928 Use MessageSerializer for 
SecurityPermissionSet (#13414)
a20d1c0f302 is described below

commit a20d1c0f3022d43230b28a7eada1748dd81e69df
Author: Ilya Shishkov <[email protected]>
AuthorDate: Fri Jul 31 10:40:07 2026 +0300

    IGNITE-28928 Use MessageSerializer for SecurityPermissionSet (#13414)
---
 .../apache/ignite/internal/MessageProcessor.java   | 127 +++++++++++++++---
 .../internal/MessageSerializerGenerator.java       |  46 ++++---
 .../ignite/internal/CoreMessagesProvider.java      |   2 +
 .../direct/stream/DirectByteBufferStream.java      |   9 ++
 .../security/SecurityBasicPermissionSet.java       |  52 +++----
 .../plugin/security/SecurityPermissionSet.java     |   3 +-
 .../internal/codegen/MessageProcessorTest.java     |  70 +++++++++-
 ...ecurityBasicPermissionSetSerializationTest.java | 149 +++++++++++++++++++++
 .../security/SecurityPermissionSetBuilderTest.java |   2 +-
 .../ignite/testsuites/SecurityTestSuite.java       |   2 +
 ...ustomEnumMapperOnNonEnumCollectionMessage.java} |  11 +-
 ...va => CustomEnumMapperOnNonEnumMapMessage.java} |  11 +-
 .../codegen/CustomMapperEnumFieldsMessage.java     |   7 +-
 .../CustomMapperEnumFieldsMessageSerializer.java   |  21 ++-
 .../codegen/DefaultMapperEnumFieldsMessage.java    |   9 ++
 .../DefaultMapperEnumFieldsMessageSerializer.java  |  46 ++++++-
 ... => DefaultMapperEnumMapCollectionMessage.java} |   9 +-
 ...a => TwoDifferentEnumsInCollectionMessage.java} |  10 +-
 .../communication/MessageCollectionItemType.java   |   5 +-
 .../extensions/communication/MessageEnumType.java  |  59 ++++++++
 20 files changed, 562 insertions(+), 88 deletions(-)

diff --git 
a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java
 
b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java
index d15090e4436..23e110b1eb4 100644
--- 
a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java
+++ 
b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -39,8 +40,10 @@ import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
 import javax.tools.Diagnostic;
 import 
org.apache.ignite.internal.systemview.SystemViewRowAttributeWalkerProcessor;
 import org.apache.ignite.internal.util.typedef.F;
@@ -294,34 +297,99 @@ public class MessageProcessor extends AbstractProcessor {
     private void validateEnumFieldMapping(TypeElement type, Element el) {
         CustomMapper custMappAnn = el.getAnnotation(CustomMapper.class);
 
-        if (enumType(processingEnv, el.asType())) {
-            String enumClsFullName = el.asType().toString();
-            String enumMapperClsName = custMappAnn != null ? 
custMappAnn.value() : DLFT_ENUM_MAPPER_CLS;
-            String msgClsName = type.toString();
+        Map<Element, String> enumsPerField = new HashMap<>();
 
-            IgniteBiTuple<String, String> otherMsgAndMapperClassesNames =
-                enumMappersInUse.put(enumClsFullName, new 
IgniteBiTuple<>(msgClsName, enumMapperClsName));
+        if (!inspectFieldForEnumTypes(type.toString(), el, el.asType(), 
custMappAnn, enumsPerField) && custMappAnn != null) {
+            processingEnv.getMessager().printMessage(
+                Diagnostic.Kind.ERROR,
+                "Annotation @CustomMapper must only be used for enum fields or 
enum collections and maps, including nested ones.",
+                el);
+        }
+    }
+
+    /**
+     * @param msgClsName Message class name currently being inspected.
+     * @param field Field being inspected.
+     * @param type Type that should be inpected for enum type (direct type or 
type parameter).
+     * @param custMappAnn Custom mapper annotation declared for the enum type.
+     * @param enumsPerField Map for collecting enum types related to a 
particular field.
+     */
+    private boolean inspectFieldForEnumTypes(String msgClsName, Element field, 
TypeMirror type, CustomMapper custMappAnn,
+        Map<Element, String> enumsPerField) {
+        String enumClsFullName = type.toString();
+        String enumMapperClsName = custMappAnn != null ? custMappAnn.value() : 
DLFT_ENUM_MAPPER_CLS;
 
-            if (otherMsgAndMapperClassesNames != null) {
-                String otherMsgClsName = otherMsgAndMapperClassesNames.get1();
-                String otherEnumMapperClsName = 
otherMsgAndMapperClassesNames.get2();
+        if (enumType(processingEnv, type)) {
+            inspectForDuplicatedMappers(msgClsName, field, enumClsFullName, 
enumMapperClsName);
 
-                if (!otherEnumMapperClsName.equals(enumMapperClsName)) {
-                    processingEnv.getMessager().printMessage(
-                        Diagnostic.Kind.ERROR,
-                        "Enum " + enumClsFullName + " is declared with 
different mappers: " +
-                            otherEnumMapperClsName + " in " + otherMsgClsName 
+ " and " +
-                            enumMapperClsName + " in " + msgClsName +
-                            ". Only one mapper is allowed per enum type.",
-                        el);
-                }
+            inspectForDuplicatedEnums(msgClsName, field, type, enumsPerField);
+
+            return true;
+        }
+        else if (assignableFrom(erasedType(type), 
type(Collection.class.getName()))) {
+            List<? extends TypeMirror> typeArgs = 
((DeclaredType)type).getTypeArguments();
+
+            assert typeArgs.size() == 1 : type.toString();
+
+            TypeMirror typeArg = typeArgs.get(0);
+
+            return inspectFieldForEnumTypes(msgClsName, field, typeArg, 
custMappAnn, enumsPerField);
+        }
+        else if (assignableFrom(erasedType(type), type(Map.class.getName()))) {
+            List<? extends TypeMirror> typeArgs = 
((DeclaredType)type).getTypeArguments();
+
+            assert typeArgs.size() == 2 : type.toString();
+
+            TypeMirror keyType = typeArgs.get(0);
+            TypeMirror valType = typeArgs.get(1);
+
+            return inspectFieldForEnumTypes(msgClsName, field, keyType, 
custMappAnn, enumsPerField) |
+                inspectFieldForEnumTypes(msgClsName, field, valType, 
custMappAnn, enumsPerField);
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks, that only single type of mapper is used for the enum type.
+     * Particular enum should be processed with a concrete enum mapper: custom 
or default one.
+     */
+    private void inspectForDuplicatedMappers(String msgClsName, Element field, 
String enumClsFullName,
+        String enumMapperClsName) {
+        IgniteBiTuple<String, String> otherMsgAndMapperClassesNames =
+            enumMappersInUse.put(enumClsFullName, new 
IgniteBiTuple<>(msgClsName, enumMapperClsName));
+
+        if (otherMsgAndMapperClassesNames != null) {
+            String otherMsgClsName = otherMsgAndMapperClassesNames.get1();
+            String otherEnumMapperClsName = 
otherMsgAndMapperClassesNames.get2();
+
+            if (!otherEnumMapperClsName.equals(enumMapperClsName)) {
+                processingEnv.getMessager().printMessage(
+                    Diagnostic.Kind.ERROR,
+                    "Enum " + enumClsFullName + " is declared with different 
mappers: " +
+                        otherEnumMapperClsName + " in " + otherMsgClsName + " 
and " +
+                        enumMapperClsName + " in " + msgClsName +
+                        ". Only one mapper is allowed per enum type.",
+                    field);
             }
         }
-        else if (custMappAnn != null) {
+    }
+
+    /**
+     * Checks that only single type of enum is introduced for a particular 
field.
+     * Multiple enum types currently are not supported for custom mapper.
+     */
+    private void inspectForDuplicatedEnums(String msgClsName, Element field, 
TypeMirror type,
+        Map<Element, String> enumsPerField) {
+        String otherEnum = type.toString();
+        String existingEnum = enumsPerField.put(field, otherEnum);
+
+        if (existingEnum != null && !Objects.equals(existingEnum, otherEnum)) {
             processingEnv.getMessager().printMessage(
                 Diagnostic.Kind.ERROR,
-                "Annotation @CustomMapper must only be used for enum fields.",
-                el);
+                String.format("Multiple enums of different types are not 
supported for a single field " +
+                        "[msgClsName=%s, field=%s, existingEnumType=%s, 
otherEnumType=%s]", msgClsName,
+                    field.getSimpleName(), existingEnum, otherEnum));
         }
     }
 
@@ -345,4 +413,21 @@ public class MessageProcessor extends AbstractProcessor {
             .flatMap(c -> 
ElementFilter.fieldsIn(c.getEnclosedElements()).stream())
             .anyMatch(f -> f.getAnnotation(Marshalled.class) != null);
     }
+
+    /** */
+    boolean assignableFrom(TypeMirror type, TypeMirror superType) {
+        return superType != null && 
processingEnv.getTypeUtils().isAssignable(type, superType);
+    }
+
+    /** */
+    TypeMirror erasedType(TypeMirror type) {
+        return processingEnv.getTypeUtils().erasure(type);
+    }
+
+    /** @return the {@link TypeMirror} for the fully-qualified {@code clazz}, 
or {@code null} if not on classpath. */
+    TypeMirror type(String clazz) {
+        Elements elementUtils = processingEnv.getElementUtils();
+        TypeElement typeElement = elementUtils.getTypeElement(clazz);
+        return typeElement != null ? typeElement.asType() : null;
+    }
 }
diff --git 
a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java
 
b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java
index d3344ea8003..6ae7e029410 100644
--- 
a/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java
+++ 
b/modules/codegen/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java
@@ -41,6 +41,7 @@ import javax.lang.model.type.PrimitiveType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import 
org.apache.ignite.internal.systemview.SystemViewRowAttributeWalkerProcessor;
+import org.apache.ignite.internal.util.typedef.F;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.MessageProcessor.CACHE_OBJECT_CLS;
@@ -110,6 +111,9 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
     /** */
     private final List<String> read = new ArrayList<>();
 
+    /** Static class fields, which should come before ordinary class fields. */
+    private final Set<String> headingClsFields = new TreeSet<>();
+
     /** Class-field declarations (enum mappers/values, collection descriptors) 
emitted at the top of the generated class. */
     private final Set<String> clsFields = new TreeSet<>();
 
@@ -261,7 +265,7 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
     /** @return Writer/reader call expression for {@code field}. */
     private String callExpr(VariableElement field, boolean write) throws 
Exception {
         if (enumType(env, field.asType())) {
-            String prefix = registerEnumMapper(field);
+            String prefix = registerEnumMapper(field, field.asType());
 
             boolean custMapper = field.getAnnotation(CustomMapper.class) != 
null;
 
@@ -425,8 +429,8 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
      *
      * @return Class-field name prefix derived from the enum type name.
      */
-    private String registerEnumMapper(VariableElement field) {
-        Element enumElem = env.getTypeUtils().asElement(field.asType());
+    private String registerEnumMapper(VariableElement field, TypeMirror 
fieldType) {
+        Element enumElem = env.getTypeUtils().asElement(fieldType);
 
         imports.add(enumElem.toString());
 
@@ -446,12 +450,12 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
 
             String simpleName = 
fullMapperName.substring(fullMapperName.lastIndexOf('.') + 1);
 
-            clsFields.add("private final EnumMapper<" + enumName + "> " + 
prefix + "Mapper = new " + simpleName + "();");
+            headingClsFields.add("private static final EnumMapper<" + enumName 
+ "> " + prefix + "Mapper = new " + simpleName + "();");
         }
         else {
             imports.add(DLFT_ENUM_MAPPER_CLS);
 
-            clsFields.add("private final " + enumName + "[] " + prefix + "Vals 
= " + enumName + ".values();");
+            headingClsFields.add("private static final " + enumName + "[] " + 
prefix + "Vals = " + enumName + ".values();");
         }
 
         return prefix;
@@ -488,7 +492,7 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
 
     /** */
     private String messageCollectionItemTypes(VariableElement field, 
TypeMirror type) throws Exception {
-        String desc = messageCollectionItemTypeDescriptor(type);
+        String desc = messageCollectionItemTypeDescriptor(type, field);
         String descName = field.getSimpleName() + "CollDesc";
         String typeName = desc.substring(desc.indexOf(' ') + 1, 
desc.indexOf('('));
 
@@ -498,7 +502,7 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
     }
 
     /** */
-    private String messageCollectionItemTypeDescriptor(TypeMirror type) throws 
Exception {
+    private String messageCollectionItemTypeDescriptor(TypeMirror type, 
VariableElement field) throws Exception {
         imports.add(MESSAGE_COLLECTION_ITEM_TYPE_CLS);
 
         if (type.getKind() == TypeKind.ARRAY) {
@@ -527,18 +531,18 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
 
             imports.add(MESSAGE_ARRAY_TYPE_CLS);
 
-            return "new MessageArrayType(" + 
messageCollectionItemTypeDescriptor(componentType) + ", " + clazz + ")";
+            return "new MessageArrayType(" + 
messageCollectionItemTypeDescriptor(componentType, field) + ", " + clazz + ")";
         }
         else if (assignableFrom(erasedType(type), type(Map.class.getName()))) {
             imports.add(MESSAGE_MAP_TYPE_CLS);
 
             List<? extends TypeMirror> typeArgs = 
((DeclaredType)type).getTypeArguments();
 
-            assert typeArgs.size() == 2;
+            assert typeArgs.size() == 2 : type.toString();
 
             return "new MessageMapType(" +
-                messageCollectionItemTypeDescriptor(typeArgs.get(0)) + ", " +
-                messageCollectionItemTypeDescriptor(typeArgs.get(1)) + ", " +
+                messageCollectionItemTypeDescriptor(typeArgs.get(0), field) + 
", " +
+                messageCollectionItemTypeDescriptor(typeArgs.get(1), field) + 
", " +
                 assignableFrom(erasedType(type), 
type(LinkedHashMap.class.getName())) + ")";
         }
         else if (assignableFrom(erasedType(type), 
type(Collection.class.getName()))) {
@@ -546,12 +550,24 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
 
             List<? extends TypeMirror> typeArgs = 
((DeclaredType)type).getTypeArguments();
 
-            assert typeArgs.size() == 1;
+            assert typeArgs.size() == 1 : type.toString();
 
             return "new MessageCollectionType(" +
-                messageCollectionItemTypeDescriptor(typeArgs.get(0)) + ", " +
+                messageCollectionItemTypeDescriptor(typeArgs.get(0), field) + 
", " +
                 assignableFrom(erasedType(type), type(Set.class.getName())) + 
")";
         }
+        else if (enumType(env, type)) {
+            
imports.add("org.apache.ignite.plugin.extensions.communication.MessageEnumType");
+
+            String prefix = registerEnumMapper(field, type);
+            boolean custMapper = field.getAnnotation(CustomMapper.class) != 
null;
+
+            String encoder = (custMapper ? prefix + "Mapper" : 
"DefaultEnumMapper.INSTANCE") + "::encode";
+            String decoder = custMapper ? prefix + "Mapper::decode" :
+                "b -> DefaultEnumMapper.INSTANCE.decode(" + prefix + "Vals, 
b)";
+
+            return String.format("new MessageEnumType<>(%s, %s)", encoder, 
decoder);
+        }
         else {
             imports.add(MESSAGE_ITEM_TYPE_CLS);
 
@@ -656,10 +672,10 @@ public class MessageSerializerGenerator extends 
MessageCompanionGenerator {
 
     /** Write serializer class fields: enum values, custom enum mappers, 
collection descriptors. */
     private void writeClassFields(Writer writer) throws IOException {
-        if (clsFields.isEmpty())
+        if (headingClsFields.isEmpty() && clsFields.isEmpty())
             return;
 
-        for (String field: clsFields) {
+        for (String field: F.concat(false, headingClsFields, clsFields)) {
             writer.write(indentedLine(METHOD_JAVADOC));
             writer.write(NL);
             writer.write(indentedLine(field));
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
index 7921779c09e..fa6514e2148 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java
@@ -270,6 +270,7 @@ import 
org.apache.ignite.internal.util.distributed.SingleNodeMessage;
 import org.apache.ignite.marshaller.Marshaller;
 import org.apache.ignite.marshaller.jdk.JdkMarshaller;
 import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.security.SecurityBasicPermissionSet;
 import org.apache.ignite.spi.collision.jobstealing.JobStealingRequest;
 import 
org.apache.ignite.spi.communication.tcp.internal.TcpConnectionRequestDiscoveryMessage;
 import 
org.apache.ignite.spi.communication.tcp.internal.TcpInverseConnectionResponseMessage;
@@ -667,6 +668,7 @@ public class CoreMessagesProvider extends 
AbstractMarshallableMessageFactoryProv
         withNoSchema(UserAuthenticateResponseMessage.class);
         withNoSchema(TcpDiscoveryAuthFailedMessage.class);
         withNoSchema(AuthentificationDataBagItem.class);
+        withNoSchema(SecurityBasicPermissionSet.class);
 
         // [12200 - 12300]: Binary, classloading and marshalling messages.
         msgIdx = 12200;
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java
index 6b673aa88d4..75440117e23 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/direct/stream/DirectByteBufferStream.java
@@ -50,6 +50,7 @@ import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.plugin.extensions.communication.MessageArrayType;
 import org.apache.ignite.plugin.extensions.communication.MessageCollectionType;
+import org.apache.ignite.plugin.extensions.communication.MessageEnumType;
 import org.apache.ignite.plugin.extensions.communication.MessageFactory;
 import org.apache.ignite.plugin.extensions.communication.MessageMapType;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
@@ -2134,6 +2135,11 @@ public class DirectByteBufferStream {
 
                 break;
 
+            case ENUM:
+                writeByte(((MessageEnumType)type).encode((Enum<?>)val));
+
+                break;
+
             case MSG:
                 writeMessage((Message)val, writer);
 
@@ -2247,6 +2253,9 @@ public class DirectByteBufferStream {
             case ARRAY:
                 return nestedRead(reader, () -> 
reader.readObjectArray((MessageArrayType)type));
 
+            case ENUM:
+                return ((MessageEnumType)type).decode(readByte());
+
             case MSG:
                 return readMessage(reader);
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityBasicPermissionSet.java
 
b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityBasicPermissionSet.java
index 87587afa90e..3daf23d9088 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityBasicPermissionSet.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityBasicPermissionSet.java
@@ -25,6 +25,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import org.apache.ignite.internal.Order;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.internal.A;
 import org.apache.ignite.internal.util.typedef.internal.S;
@@ -45,24 +46,29 @@ public class SecurityBasicPermissionSet implements 
SecurityPermissionSet {
 
     /** Cache permissions. */
     @GridToStringInclude
-    private Map<String, Collection<SecurityPermission>> cachePermissions = new 
HashMap<>();
+    @Order(0)
+    Map<String, Collection<SecurityPermission>> cachePermissions = new 
HashMap<>();
 
     /** Task permissions. */
     @GridToStringInclude
-    private Map<String, Collection<SecurityPermission>> taskPermissions = new 
HashMap<>();
+    @Order(1)
+    Map<String, Collection<SecurityPermission>> taskPermissions = new 
HashMap<>();
 
     /** Service permissions. */
     @GridToStringInclude
-    private transient Map<String, Collection<SecurityPermission>> 
servicePermissions = isSecurityCompatibilityMode()
+    @Order(2)
+    transient Map<String, Collection<SecurityPermission>> srvcPermissions = 
isSecurityCompatibilityMode()
             ? compatibleServicePermissions()
-            : new HashMap<String, Collection<SecurityPermission>>();
+            : new HashMap<>();
 
     /** System permissions. */
     @GridToStringInclude
-    private Collection<SecurityPermission> systemPermissions;
+    @Order(3)
+    Collection<SecurityPermission> sysPermissions;
 
     /** Default allow all. */
-    private boolean dfltAllowAll;
+    @Order(4)
+    boolean dfltAllowAll;
 
     /**
      * Setter for set cache permission map.
@@ -89,21 +95,21 @@ public class SecurityBasicPermissionSet implements 
SecurityPermissionSet {
     /**
      * Setter for set service permission map.
      *
-     * @param servicePermissions Service permissions.
+     * @param srvcPermissions Service permissions.
      */
-    public void setServicePermissions(Map<String, 
Collection<SecurityPermission>> servicePermissions) {
+    public void setServicePermissions(Map<String, 
Collection<SecurityPermission>> srvcPermissions) {
         A.notNull(taskPermissions, "servicePermissions");
 
-        this.servicePermissions = servicePermissions;
+        this.srvcPermissions = srvcPermissions;
     }
 
     /**
      * Setter for set collection system permission.
      *
-     * @param systemPermissions System permissions.
+     * @param sysPermissions System permissions.
      */
-    public void setSystemPermissions(Collection<SecurityPermission> 
systemPermissions) {
-        this.systemPermissions = systemPermissions;
+    public void setSystemPermissions(Collection<SecurityPermission> 
sysPermissions) {
+        this.sysPermissions = sysPermissions;
     }
 
     /**
@@ -127,12 +133,12 @@ public class SecurityBasicPermissionSet implements 
SecurityPermissionSet {
 
     /** {@inheritDoc} */
     @Override public Map<String, Collection<SecurityPermission>> 
servicePermissions() {
-        return servicePermissions;
+        return srvcPermissions;
     }
 
     /** {@inheritDoc} */
     @Nullable @Override public Collection<SecurityPermission> 
systemPermissions() {
-        return systemPermissions;
+        return sysPermissions;
     }
 
     /** {@inheritDoc} */
@@ -153,8 +159,8 @@ public class SecurityBasicPermissionSet implements 
SecurityPermissionSet {
         return dfltAllowAll == other.dfltAllowAll &&
             Objects.equals(cachePermissions, other.cachePermissions) &&
             Objects.equals(taskPermissions, other.taskPermissions) &&
-            Objects.equals(servicePermissions, other.servicePermissions) &&
-            Objects.equals(systemPermissions, other.systemPermissions);
+            Objects.equals(srvcPermissions, other.srvcPermissions) &&
+            Objects.equals(sysPermissions, other.sysPermissions);
     }
 
     /** {@inheritDoc} */
@@ -163,8 +169,8 @@ public class SecurityBasicPermissionSet implements 
SecurityPermissionSet {
 
         res = 31 * res + (cachePermissions != null ? 
cachePermissions.hashCode() : 0);
         res = 31 * res + (taskPermissions != null ? taskPermissions.hashCode() 
: 0);
-        res = 31 * res + (servicePermissions != null ? 
servicePermissions.hashCode() : 0);
-        res = 31 * res + (systemPermissions != null ? 
systemPermissions.hashCode() : 0);
+        res = 31 * res + (srvcPermissions != null ? srvcPermissions.hashCode() 
: 0);
+        res = 31 * res + (sysPermissions != null ? sysPermissions.hashCode() : 
0);
 
         return res;
     }
@@ -176,7 +182,7 @@ public class SecurityBasicPermissionSet implements 
SecurityPermissionSet {
         out.defaultWriteObject();
 
         if (serializeVersion() >= 2)
-            U.writeMap(out, servicePermissions);
+            U.writeMap(out, srvcPermissions);
     }
 
     /**
@@ -186,14 +192,14 @@ public class SecurityBasicPermissionSet implements 
SecurityPermissionSet {
         in.defaultReadObject();
 
         if (serializeVersion() >= 2)
-            servicePermissions = U.readMap(in);
+            srvcPermissions = U.readMap(in);
 
-        if (servicePermissions == null) {
+        if (srvcPermissions == null) {
             // Allow all for compatibility mode
             if (serializeVersion() < 2)
-                servicePermissions = compatibleServicePermissions();
+                srvcPermissions = compatibleServicePermissions();
             else
-                servicePermissions = Collections.emptyMap();
+                srvcPermissions = Collections.emptyMap();
         }
     }
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermissionSet.java
 
b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermissionSet.java
index 96340d8d149..20e78c6efab 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermissionSet.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermissionSet.java
@@ -20,6 +20,7 @@ package org.apache.ignite.plugin.security;
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.Map;
+import org.apache.ignite.plugin.extensions.communication.Message;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -31,7 +32,7 @@ import org.jetbrains.annotations.Nullable;
  * Property {@link #defaultAllowAll()} specifies whether to allow or deny
  * cache and task operations if they were not explicitly specified.
  */
-public interface SecurityPermissionSet extends Serializable {
+public interface SecurityPermissionSet extends Serializable, Message {
     /**
      * Flag indicating whether to allow or deny cache and task operations
      * if they were not explicitly specified.
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java
index 30901aefd3d..24b8232cdd1 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java
@@ -51,6 +51,10 @@ import static org.junit.Assert.assertTrue;
 
 /** */
 public class MessageProcessorTest {
+    /** Custom mapper error. */
+    public static final String CUSTOM_MAPPER_ERROR = "Annotation @CustomMapper 
must only be used for enum fields or " +
+        "enum collections and maps, including nested ones.";
+
     /** */
     @Test
     public void testMarshalledObjectsMessage() {
@@ -273,7 +277,7 @@ public class MessageProcessorTest {
         Compilation compilation = 
compile("CustomEnumMapperOnPrimitiveFieldMessage.java");
 
         assertThat(compilation).failed();
-        assertThat(compilation).hadErrorContaining("Annotation @CustomMapper 
must only be used for enum fields.");
+        assertThat(compilation).hadErrorContaining(CUSTOM_MAPPER_ERROR);
     }
 
     /**
@@ -285,7 +289,50 @@ public class MessageProcessorTest {
         Compilation compilation = 
compile("CustomEnumMapperOnArrayFieldMessage.java");
 
         assertThat(compilation).failed();
-        assertThat(compilation).hadErrorContaining("Annotation @CustomMapper 
must only be used for enum fields.");
+        assertThat(compilation).hadErrorContaining(CUSTOM_MAPPER_ERROR);
+    }
+
+    /**
+     * Negative test for CustomMapper annotation verifying an error is thrown 
by codegeneration tool if
+     * the annotation is used with a collection, which does not store enums.
+     */
+    @Test
+    public void testCustomMapperCannotBeUsedOnNonEnumCollection() {
+        Compilation compilation = 
compile("CustomEnumMapperOnArrayFieldMessage.java");
+
+        assertThat(compilation).failed();
+        assertThat(compilation).hadErrorContaining(CUSTOM_MAPPER_ERROR);
+    }
+
+    /**
+     * Negative test for CustomMapper annotation verifying an error is thrown 
by codegeneration tool if
+     * the annotation is used with a collection, which does not store enums.
+     */
+    @Test
+    public void testCustomMapperCannotBeUsedOnNonEnumMap() {
+        Compilation compilation = 
compile("CustomEnumMapperOnNonEnumMapMessage.java");
+
+        assertThat(compilation).failed();
+        assertThat(compilation).hadErrorContaining(CUSTOM_MAPPER_ERROR);
+    }
+
+    /**
+     * Negative test for enum mapper verifying an error is thrown by 
codegeneration tool if
+     * the annotation is used with a collection with nested enums of different 
type.
+     */
+    @Test
+    public void testEnumMapperCannotBeTwoEnums() {
+        Compilation compilation = 
compile("TwoDifferentEnumsInCollectionMessage.java");
+
+        assertThat(compilation).failed();
+
+        String oai = "org.apache.ignite.";
+
+        assertThat(compilation).hadErrorContaining(String.format("Multiple 
enums of different types are not supported " +
+            "for a single field [msgClsName=%s, field=%s, existingEnumType=%s, 
otherEnumType=%s]",
+            oai + "internal.TwoDifferentEnumsInCollectionMessage", "col",
+            oai + "transactions.TransactionIsolation",
+            oai + "internal.processors.cache.GridCacheOperation"));
     }
 
     /**
@@ -343,6 +390,25 @@ public class MessageProcessorTest {
         assertThat(compilation).hadErrorContaining(errMsg);
     }
 
+    /**
+     * Negative test for a conflict situation when two enum mappers are used 
for the same enum in different messages.
+     * Tests conflict between ordinary enum field and collection of enums.
+     */
+    @Test
+    public void 
testDifferentMappersForTheSameEnumAreProhibitedWithCollection() {
+        Compilation compilation = 
compile("DefaultMapperEnumMapCollectionMessage.java",
+            "CustomMapperEnumFieldsMessage.java",
+            "TransactionIsolationEnumMapper.java");
+
+        assertThat(compilation).failed();
+
+        String errMsg = "Enum " + TransactionIsolation.class.getName() + " is 
declared with different mappers: " +
+            DefaultEnumMapper.class.getName() + " in 
org.apache.ignite.internal.DefaultMapperEnumMapCollectionMessage" +
+            " and org.apache.ignite.internal.TransactionIsolationEnumMapper in 
org.apache.ignite.internal.CustomMapperEnumFieldsMessage.";
+
+        assertThat(compilation).hadErrorContaining(errMsg);
+    }
+
     /**
      * Positive test verifies that codegeneration is successful when two 
messages use DefaultEnumMapper for the same enum type.
      */
diff --git 
a/modules/core/src/test/java/org/apache/ignite/plugin/security/SecurityBasicPermissionSetSerializationTest.java
 
b/modules/core/src/test/java/org/apache/ignite/plugin/security/SecurityBasicPermissionSetSerializationTest.java
new file mode 100644
index 00000000000..5d94d4e8847
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/plugin/security/SecurityBasicPermissionSetSerializationTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.plugin.security;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.CoreMessagesProvider;
+import org.apache.ignite.internal.direct.DirectMessageReader;
+import org.apache.ignite.internal.direct.DirectMessageWriter;
+import 
org.apache.ignite.internal.managers.communication.IgniteMessageFactoryImpl;
+import org.apache.ignite.internal.util.nio.MessageSerialization;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.extensions.communication.MessageFactory;
+import 
org.apache.ignite.plugin.extensions.communication.MessageFactoryProvider;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+import static org.apache.ignite.internal.util.CommonUtils.makeMessageType;
+import static org.apache.ignite.marshaller.Marshallers.jdk;
+import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_CACHE;
+import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_QUERY;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.CACHE_CREATE;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_PUT;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.SERVICE_CANCEL;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.SERVICE_INVOKE;
+import static org.apache.ignite.plugin.security.SecurityPermission.TASK_CANCEL;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.TASK_EXECUTE;
+
+/** Test for serialization round-trip of {@link SecurityBasicPermissionSet}. */
+public class SecurityBasicPermissionSetSerializationTest extends 
GridCommonAbstractTest {
+    /** */
+    private final MessageFactory<? extends Message> msgFactory = new 
IgniteMessageFactoryImpl<>(
+        new MessageFactoryProvider[] {new CoreMessagesProvider(jdk(), jdk())});
+
+    /** */
+    @Test
+    public void testWriteAndReadBack() throws Exception {
+        SecurityBasicPermissionSet src = new SecurityBasicPermissionSet();
+
+        src.setDefaultAllowAll(true);
+        src.setSystemPermissions(F.asList(ADMIN_CACHE, ADMIN_QUERY, null));
+        src.setTaskPermissions(Map.of("task", F.asList(TASK_EXECUTE, null, 
TASK_CANCEL)));
+        src.setServicePermissions(Map.of("service", Set.of(SERVICE_INVOKE, 
SERVICE_CANCEL)));
+        src.setCachePermissions(Map.of("cache", Set.of(CACHE_CREATE, 
CACHE_PUT)));
+
+        src.setCachePermissions(Map.of("cache", Set.of(CACHE_CREATE, 
CACHE_PUT)));
+        SecurityBasicPermissionSet res = writeAndReadBack(src);
+
+        assertTrue("Permission sets are not equal [src=" + src + ", res=" + 
res + "]", deepEquals(src, res));
+    }
+
+    /**
+     * @param msg Message to write and read back through {@link 
DirectMessageWriter}/{@link DirectMessageReader}.
+     * @param <T> Type of Message.
+     *
+     * @return Restored message.
+     */
+    private <T extends Message> T writeAndReadBack(T msg) throws 
IgniteCheckedException {
+        GridTestKernalContext kctx = newContext();
+
+        GridTestUtils.setFieldValue(kctx.grid(), "msgFactory", msgFactory);
+
+        ByteBuffer buf = ByteBuffer.allocate(64 * 1024);
+
+        DirectMessageWriter writer = new DirectMessageWriter(msgFactory);
+        writer.setBuffer(buf);
+
+        assertTrue(MessageSerialization.writeTo(msgFactory, msg, writer));
+
+        buf.flip();
+
+        DirectMessageReader reader = new DirectMessageReader(msgFactory, null);
+        reader.setBuffer(buf);
+
+        T res = (T)msgFactory.create(makeMessageType(buf.get(), buf.get()));
+
+        assertTrue(MessageSerialization.readFrom(msgFactory, res, reader));
+
+        return res;
+    }
+
+    /**
+     * Perfroms deep equals of permission sets.
+     *
+     * @param lhs First permissions set for equality check.
+     * @param rhs Second permissions set for equality check.
+     * @return Whether specified permission sets are equal.
+     */
+    public static boolean deepEquals(SecurityPermissionSet lhs, 
SecurityPermissionSet rhs) {
+        if (lhs == rhs)
+            return true;
+
+        return lhs != null
+            && rhs != null
+            && lhs.defaultAllowAll() == rhs.defaultAllowAll()
+            && (F.isEmpty(rhs.systemPermissions()) && 
F.isEmpty(rhs.systemPermissions())
+            || F.eqNotOrdered(rhs.systemPermissions(), 
lhs.systemPermissions()))
+            && eqNotOrdered(rhs.taskPermissions(), lhs.taskPermissions())
+            && eqNotOrdered(rhs.servicePermissions(), lhs.servicePermissions())
+            && eqNotOrdered(rhs.cachePermissions(), lhs.cachePermissions());
+    }
+
+    /**
+     * @param m1 First map to check.
+     * @param m2 Second map to check
+     * @return {@code True} is maps are equal, {@code False} otherwise.
+     */
+    public static boolean eqNotOrdered(
+        @Nullable Map<String, Collection<SecurityPermission>> m1,
+        @Nullable Map<String, Collection<SecurityPermission>> m2) {
+        if (m1 == m2)
+            return true;
+
+        if (m1 == null || m2 == null)
+            return false;
+
+        if (m1.size() != m2.size())
+            return false;
+
+        for (Map.Entry<String, Collection<SecurityPermission>> e : 
m1.entrySet()) {
+            if (!F.eqNotOrdered(e.getValue(), m2.get(e.getKey())))
+                return false;
+        }
+
+        return true;
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/plugin/security/SecurityPermissionSetBuilderTest.java
 
b/modules/core/src/test/java/org/apache/ignite/plugin/security/SecurityPermissionSetBuilderTest.java
index 71a63938daf..04fee907d95 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/plugin/security/SecurityPermissionSetBuilderTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/plugin/security/SecurityPermissionSetBuilderTest.java
@@ -143,7 +143,7 @@ public class SecurityPermissionSetBuilderTest extends 
GridCommonAbstractTest {
      * @param perms Permissions.
      * @return Collection.
      */
-    private static Collection<SecurityPermission> 
permissions(SecurityPermission... perms) {
+    static Collection<SecurityPermission> permissions(SecurityPermission... 
perms) {
         Collection<SecurityPermission> col = U.newHashSet(perms.length);
 
         Collections.addAll(col, perms);
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
index 2f34fcc88d8..375908bcca8 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
@@ -76,6 +76,7 @@ import 
org.apache.ignite.internal.processors.security.service.ServiceStaticConfi
 import 
org.apache.ignite.internal.processors.security.snapshot.SnapshotPermissionCheckTest;
 import 
org.apache.ignite.internal.thread.context.OperationContextAttributePropagationTest;
 import 
org.apache.ignite.internal.thread.context.OperationContextAttributesTest;
+import 
org.apache.ignite.plugin.security.SecurityBasicPermissionSetSerializationTest;
 import org.apache.ignite.ssl.MultipleSSLContextsTest;
 import org.apache.ignite.tools.junit.JUnitTeamcityReporter;
 import org.junit.BeforeClass;
@@ -151,6 +152,7 @@ import org.junit.runners.Suite;
     NodeConnectionCertificateCapturingTest.class,
     OperationContextAttributesTest.class,
     OperationContextAttributePropagationTest.class,
+    SecurityBasicPermissionSetSerializationTest.class,
 })
 public class SecurityTestSuite {
     /** */
diff --git 
a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java 
b/modules/core/src/test/resources/codegen/CustomEnumMapperOnNonEnumCollectionMessage.java
similarity index 80%
copy from 
modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
copy to 
modules/core/src/test/resources/codegen/CustomEnumMapperOnNonEnumCollectionMessage.java
index 1cdf20fc579..8f5117fb571 100644
--- a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
+++ 
b/modules/core/src/test/resources/codegen/CustomEnumMapperOnNonEnumCollectionMessage.java
@@ -17,15 +17,16 @@
 
 package org.apache.ignite.internal;
 
+import java.util.Collection;
 import org.apache.ignite.plugin.extensions.communication.Message;
-import org.apache.ignite.transactions.TransactionIsolation;
 
-public class CustomMapperEnumFieldsMessage implements Message {
+public class CustomEnumMapperOnNonEnumCollectionMessage implements Message {
     @Order(0)
-    @CustomMapper("org.apache.ignite.internal.TransactionIsolationEnumMapper")
-    TransactionIsolation txMode;
+    @CustomMapper("org.apache.ignite.internal.CustomEnumMapper")
+    private Collection<Integer> intColField;
 
+    @Override
     public short directType() {
         return 0;
     }
-}
\ No newline at end of file
+}
diff --git 
a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java 
b/modules/core/src/test/resources/codegen/CustomEnumMapperOnNonEnumMapMessage.java
similarity index 80%
copy from 
modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
copy to 
modules/core/src/test/resources/codegen/CustomEnumMapperOnNonEnumMapMessage.java
index 1cdf20fc579..fce8f0c34d0 100644
--- a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
+++ 
b/modules/core/src/test/resources/codegen/CustomEnumMapperOnNonEnumMapMessage.java
@@ -17,15 +17,16 @@
 
 package org.apache.ignite.internal;
 
+import java.util.Map;
 import org.apache.ignite.plugin.extensions.communication.Message;
-import org.apache.ignite.transactions.TransactionIsolation;
 
-public class CustomMapperEnumFieldsMessage implements Message {
+public class CustomEnumMapperOnNonEnumMapMessage implements Message {
     @Order(0)
-    @CustomMapper("org.apache.ignite.internal.TransactionIsolationEnumMapper")
-    TransactionIsolation txMode;
+    @CustomMapper("org.apache.ignite.internal.CustomEnumMapper")
+    Map<String, Integer> intColField;
 
+    @Override
     public short directType() {
         return 0;
     }
-}
\ No newline at end of file
+}
diff --git 
a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java 
b/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
index 1cdf20fc579..b59b931780d 100644
--- a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
+++ b/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal;
 
+import java.util.Collection;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.transactions.TransactionIsolation;
 
@@ -25,7 +26,11 @@ public class CustomMapperEnumFieldsMessage implements 
Message {
     @CustomMapper("org.apache.ignite.internal.TransactionIsolationEnumMapper")
     TransactionIsolation txMode;
 
+    @Order(1)
+    @CustomMapper("org.apache.ignite.internal.TransactionIsolationEnumMapper")
+    Collection<Collection<TransactionIsolation>> isolations;
+
     public short directType() {
         return 0;
     }
-}
\ No newline at end of file
+}
diff --git 
a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessageSerializer.java
 
b/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessageSerializer.java
index 8d572106c43..270f40fc71b 100644
--- 
a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessageSerializer.java
+++ 
b/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessageSerializer.java
@@ -19,6 +19,9 @@ package org.apache.ignite.internal;
 
 import org.apache.ignite.internal.CustomMapperEnumFieldsMessage;
 import org.apache.ignite.internal.TransactionIsolationEnumMapper;
+import 
org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionType;
+import org.apache.ignite.plugin.extensions.communication.MessageEnumType;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageSerializer;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
@@ -32,7 +35,9 @@ import org.apache.ignite.transactions.TransactionIsolation;
  */
 public final class CustomMapperEnumFieldsMessageSerializer implements 
MessageSerializer<CustomMapperEnumFieldsMessage> {
     /** */
-    private final EnumMapper<TransactionIsolation> transactionIsolationMapper 
= new TransactionIsolationEnumMapper();
+    private static final EnumMapper<TransactionIsolation> 
transactionIsolationMapper = new TransactionIsolationEnumMapper();
+    /** */
+    private static final MessageCollectionType isolationsCollDesc = new 
MessageCollectionType(new MessageCollectionType(new 
MessageEnumType<>(transactionIsolationMapper::encode, 
transactionIsolationMapper::decode), false), false);
 
     /** */
     @Override public final boolean writeTo(CustomMapperEnumFieldsMessage msg, 
MessageWriter writer) {
@@ -49,6 +54,12 @@ public final class CustomMapperEnumFieldsMessageSerializer 
implements MessageSer
                     return false;
 
                 writer.incrementState();
+
+            case 1:
+                if (!writer.writeCollection(msg.isolations, 
isolationsCollDesc))
+                    return false;
+
+                writer.incrementState();
         }
 
         return true;
@@ -60,6 +71,14 @@ public final class CustomMapperEnumFieldsMessageSerializer 
implements MessageSer
             case 0:
                 msg.txMode = 
transactionIsolationMapper.decode(reader.readByte());
 
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 1:
+                msg.isolations = reader.readCollection(isolationsCollDesc);
+
                 if (!reader.isLastRead())
                     return false;
 
diff --git 
a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java 
b/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java
index 9af3651e327..4484d8b31ca 100644
--- 
a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java
+++ 
b/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java
@@ -17,7 +17,10 @@
 
 package org.apache.ignite.internal;
 
+import java.util.Collection;
+import java.util.Map;
 import org.apache.ignite.internal.processors.cache.GridCacheOperation;
+import org.apache.ignite.internal.processors.cache.verify.PartitionHashRecord;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.transactions.TransactionIsolation;
 
@@ -28,6 +31,12 @@ public class DefaultMapperEnumFieldsMessage implements 
Message {
     @Order(1)
     GridCacheOperation internalEnum;
 
+    @Order(2)
+    Map<Collection<TransactionIsolation>, String> isolationStringMap;
+
+    @Order(3)
+    Collection<PartitionHashRecord.PartitionState> partStates;
+
     public short directType() {
         return 0;
     }
diff --git 
a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java
 
b/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java
index da05e0d2408..25324b44875 100644
--- 
a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java
+++ 
b/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java
@@ -19,6 +19,12 @@ package org.apache.ignite.internal;
 
 import org.apache.ignite.internal.DefaultMapperEnumFieldsMessage;
 import org.apache.ignite.internal.processors.cache.GridCacheOperation;
+import 
org.apache.ignite.internal.processors.cache.verify.PartitionHashRecord.PartitionState;
+import 
org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionType;
+import org.apache.ignite.plugin.extensions.communication.MessageEnumType;
+import org.apache.ignite.plugin.extensions.communication.MessageItemType;
+import org.apache.ignite.plugin.extensions.communication.MessageMapType;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageSerializer;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
@@ -32,9 +38,15 @@ import org.apache.ignite.transactions.TransactionIsolation;
  */
 public final class DefaultMapperEnumFieldsMessageSerializer implements 
MessageSerializer<DefaultMapperEnumFieldsMessage> {
     /** */
-    private final GridCacheOperation[] gridCacheOperationVals = 
GridCacheOperation.values();
+    private static final GridCacheOperation[] gridCacheOperationVals = 
GridCacheOperation.values();
     /** */
-    private final TransactionIsolation[] transactionIsolationVals = 
TransactionIsolation.values();
+    private static final PartitionState[] partitionStateVals = 
PartitionState.values();
+    /** */
+    private static final TransactionIsolation[] transactionIsolationVals = 
TransactionIsolation.values();
+    /** */
+    private static final MessageCollectionType partStatesCollDesc = new 
MessageCollectionType(new MessageEnumType<>(DefaultEnumMapper.INSTANCE::encode, 
b -> DefaultEnumMapper.INSTANCE.decode(partitionStateVals, b)), false);
+    /** */
+    private static final MessageMapType isolationStringMapCollDesc = new 
MessageMapType(new MessageCollectionType(new 
MessageEnumType<>(DefaultEnumMapper.INSTANCE::encode, b -> 
DefaultEnumMapper.INSTANCE.decode(transactionIsolationVals, b)), false), new 
MessageItemType(MessageCollectionItemType.STRING), false);
 
     /** */
     @Override public final boolean writeTo(DefaultMapperEnumFieldsMessage msg, 
MessageWriter writer) {
@@ -57,6 +69,18 @@ public final class DefaultMapperEnumFieldsMessageSerializer 
implements MessageSe
                     return false;
 
                 writer.incrementState();
+
+            case 2:
+                if (!writer.writeMap(msg.isolationStringMap, 
isolationStringMapCollDesc))
+                    return false;
+
+                writer.incrementState();
+
+            case 3:
+                if (!writer.writeCollection(msg.partStates, 
partStatesCollDesc))
+                    return false;
+
+                writer.incrementState();
         }
 
         return true;
@@ -76,6 +100,22 @@ public final class DefaultMapperEnumFieldsMessageSerializer 
implements MessageSe
             case 1:
                 msg.internalEnum = 
DefaultEnumMapper.INSTANCE.decode(gridCacheOperationVals, reader.readByte());
 
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 2:
+                msg.isolationStringMap = 
reader.readMap(isolationStringMapCollDesc);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 3:
+                msg.partStates = reader.readCollection(partStatesCollDesc);
+
                 if (!reader.isLastRead())
                     return false;
 
@@ -89,4 +129,4 @@ public final class DefaultMapperEnumFieldsMessageSerializer 
implements MessageSe
     @Override public final DefaultMapperEnumFieldsMessage createMessage() {
         return new DefaultMapperEnumFieldsMessage();
     }
-}
\ No newline at end of file
+}
diff --git 
a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java 
b/modules/core/src/test/resources/codegen/DefaultMapperEnumMapCollectionMessage.java
similarity index 84%
copy from 
modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
copy to 
modules/core/src/test/resources/codegen/DefaultMapperEnumMapCollectionMessage.java
index 1cdf20fc579..9e5ebb7a03c 100644
--- a/modules/core/src/test/resources/codegen/CustomMapperEnumFieldsMessage.java
+++ 
b/modules/core/src/test/resources/codegen/DefaultMapperEnumMapCollectionMessage.java
@@ -17,15 +17,16 @@
 
 package org.apache.ignite.internal;
 
+import java.util.Collection;
+import java.util.Map;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.transactions.TransactionIsolation;
 
-public class CustomMapperEnumFieldsMessage implements Message {
+public class DefaultMapperEnumMapCollectionMessage implements Message {
     @Order(0)
-    @CustomMapper("org.apache.ignite.internal.TransactionIsolationEnumMapper")
-    TransactionIsolation txMode;
+    Map<Collection<TransactionIsolation>, String> isolationStringMap;
 
     public short directType() {
         return 0;
     }
-}
\ No newline at end of file
+}
diff --git 
a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java 
b/modules/core/src/test/resources/codegen/TwoDifferentEnumsInCollectionMessage.java
similarity index 84%
copy from 
modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java
copy to 
modules/core/src/test/resources/codegen/TwoDifferentEnumsInCollectionMessage.java
index 9af3651e327..c36e8b8703b 100644
--- 
a/modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java
+++ 
b/modules/core/src/test/resources/codegen/TwoDifferentEnumsInCollectionMessage.java
@@ -17,17 +17,17 @@
 
 package org.apache.ignite.internal;
 
+import java.util.Collection;
+import java.util.Map;
 import org.apache.ignite.internal.processors.cache.GridCacheOperation;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.transactions.TransactionIsolation;
 
-public class DefaultMapperEnumFieldsMessage implements Message {
+public class TwoDifferentEnumsInCollectionMessage implements Message {
     @Order(0)
-    TransactionIsolation publicEnum;
-
-    @Order(1)
-    GridCacheOperation internalEnum;
+    private Collection<Map<TransactionIsolation, GridCacheOperation>> col;
 
+    @Override
     public short directType() {
         return 0;
     }
diff --git 
a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageCollectionItemType.java
 
b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageCollectionItemType.java
index b98e79a46af..729c1e09df0 100644
--- 
a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageCollectionItemType.java
+++ 
b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageCollectionItemType.java
@@ -108,7 +108,10 @@ public enum MessageCollectionItemType {
     COLLECTION,
 
     /** Array. */
-    ARRAY;
+    ARRAY,
+
+    /** Enum. */
+    ENUM;
 
     /** Enum values. */
     private static final MessageCollectionItemType[] VALS = values();
diff --git 
a/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageEnumType.java
 
b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageEnumType.java
new file mode 100644
index 00000000000..cdf67ea5649
--- /dev/null
+++ 
b/modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/MessageEnumType.java
@@ -0,0 +1,59 @@
+/*
+ * 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.plugin.extensions.communication;
+
+import java.util.function.Function;
+
+/** */
+public class MessageEnumType<T extends Enum<T>> implements MessageType {
+    /** */
+    private final Function<T, Byte> encoder;
+
+    /** */
+    private final Function<Byte, T> decoder;
+
+    /**
+     * @param encoder Encoder.
+     * @param decoder Decoder.
+     */
+    public MessageEnumType(Function<T, Byte> encoder, Function<Byte, T> 
decoder) {
+        this.encoder = encoder;
+        this.decoder = decoder;
+    }
+
+    /**
+     * @param val Value.
+     * @return Encoded value.
+     */
+    public byte encode(T val) {
+        return encoder.apply(val);
+    }
+
+    /**
+     * @param b Byte representation of enum value.
+     * @return Corresponding enum value.
+     */
+    public T decode(byte b) {
+        return decoder.apply(b);
+    }
+
+    /** {@inheritDoc} */
+    @Override public MessageCollectionItemType type() {
+        return MessageCollectionItemType.ENUM;
+    }
+}

Reply via email to