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

sk0x50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new e65f5cf532 IGNITE-19539 Add exception mapper utility (#2225)
e65f5cf532 is described below

commit e65f5cf5329ab90f048034ef2b826a5ed1845554
Author: Slava Koptilin <slava.kopti...@gmail.com>
AuthorDate: Wed Jun 28 16:36:44 2023 +0300

    IGNITE-19539 Add exception mapper utility (#2225)
---
 modules/api/build.gradle                           |   3 +
 .../apache/ignite/lang/IgniteExceptionMapper.java  |  95 ++++++++++
 .../ignite/lang/IgniteExceptionMapperUtil.java     | 140 ++++++++++++++
 .../lang/IgniteExceptionMappersProvider.java       |  48 +++++
 .../apache/ignite/lang/IgniteExceptionUtils.java   |  12 +-
 .../java/org/apache/ignite/tx/Transaction.java     |   4 +-
 .../ignite/lang/IgniteExceptionMapperUtilTest.java | 204 +++++++++++++++++++++
 .../lang/TestIgniteExceptionMappersProvider.java   |  52 ++++++
 .../ignite/configuration/ConfigurationModule.java  |   2 +-
 9 files changed, 552 insertions(+), 8 deletions(-)

diff --git a/modules/api/build.gradle b/modules/api/build.gradle
index c53932e48f..b46903b23b 100644
--- a/modules/api/build.gradle
+++ b/modules/api/build.gradle
@@ -29,8 +29,11 @@ dependencies {
     testImplementation libs.hamcrest.optional
     testImplementation libs.archunit.core
     testImplementation libs.archunit.junit5
+    testImplementation libs.auto.service.annotations
     testImplementation testFixtures(project(":ignite-core"))
 
+    testAnnotationProcessor libs.auto.service
+
     testFixturesAnnotationProcessor libs.micronaut.inject.annotation.processor
     testFixturesImplementation project(":ignite-core")
     testFixturesImplementation testFixtures(project(":ignite-core"))
diff --git 
a/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMapper.java 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMapper.java
new file mode 100755
index 0000000000..f50cb604b3
--- /dev/null
+++ 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMapper.java
@@ -0,0 +1,95 @@
+/*
+ * 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.lang;
+
+import java.util.function.Function;
+
+/**
+ * Represents a mapper from an internal exception {@code T} to a public one 
{@code R}.
+ */
+public class IgniteExceptionMapper<T extends Exception, R extends Exception> {
+    /** Class that represents an internal exception for mapping. */
+    private final Class<T> from;
+
+    /** Mapping function. */
+    private final Function<T, R> mapper;
+
+    /**
+     * Creates a new instance of mapper.
+     *
+     * @param from Class instance that represents a class of internal 
exception.
+     * @param mapper Mapping function to map an internal exception to a public 
one.
+     */
+    private IgniteExceptionMapper(Class<T> from, Function<T, R> mapper) {
+        this.from = from;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Returns a class instance that represents an internal exception to be 
used for mapping.
+     *
+     * @return Class instance that represents an internal exception to be used 
for mapping.
+     */
+    public Class<T> mappingFrom() {
+        return from;
+    }
+
+    /**
+     * Maps the provided internal exception to a public one.
+     *
+     * @param exception Exception instance to be mapped.
+     * @return Public exception instance.
+     */
+    public R map(T exception) {
+        return mapper.apply(exception);
+    }
+
+    /**
+     * Creates a new exception mapper from an internal exception {@code T} to 
a public runtime exception {@code R}.
+     *
+     * @param from Class instance that represents a class of internal 
exception.
+     * @param mapper Mapping function to map an internal exception to a public 
one.
+     * @param <T> Internal exception type.
+     * @param <R> Public runtime exception type.
+     *
+     * @return New instance of {@link IgniteExceptionMapper}.
+     */
+    public static <T extends Exception, R extends IgniteException> 
IgniteExceptionMapper<T, R> unchecked(
+            Class<T> from,
+            Function<T, R> mapper
+    ) {
+        return new IgniteExceptionMapper<T, R>(from, mapper);
+    }
+
+    /**
+     * Creates a new exception mapper from an internal exception {@code T} to 
a public checked exception {@code R}.
+     *
+     * @param from Class instance that represents a class of internal 
exception.
+     * @param mapper Mapping function to map an internal exception to a public 
one.
+     * @param <T> Internal exception type.
+     * @param <R> Public checked exception type.
+     *
+     * @return New instance of {@link IgniteExceptionMapper}.
+     */
+    public static <T extends Exception, R extends IgniteCheckedException> 
IgniteExceptionMapper<T, R> checked(
+            Class<T> from,
+            Function<T, R> mapper
+    ) {
+        return new IgniteExceptionMapper<T, R>(from, mapper);
+    }
+}
diff --git 
a/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMapperUtil.java
 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMapperUtil.java
new file mode 100755
index 0000000000..869daa8801
--- /dev/null
+++ 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMapperUtil.java
@@ -0,0 +1,140 @@
+/*
+ * 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.lang;
+
+import static java.util.Collections.unmodifiableMap;
+import static org.apache.ignite.internal.util.ExceptionUtils.unwrapCause;
+import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+/**
+ * This utility class provides an ability to map Ignite internal exceptions to 
Ignite public ones.
+ */
+public class IgniteExceptionMapperUtil {
+    /** All exception mappers to be used to map internal exceptions to public 
ones. */
+    private static final Map<Class<? extends Exception>, 
IgniteExceptionMapper<?, ?>> EXCEPTION_CONVERTERS;
+
+    static {
+        Map<Class<? extends Exception>, IgniteExceptionMapper<?, ?>> mappers = 
new HashMap<>();
+
+        ServiceLoader
+                .load(IgniteExceptionMappersProvider.class)
+                .forEach(provider -> provider.mappers().forEach(m -> 
registerMapping(m, mappers)));
+
+        EXCEPTION_CONVERTERS = unmodifiableMap(mappers);
+    }
+
+    /**
+     * Add a new mapping to already registered ones.
+     *
+     * @param mapper Exception mapper from internal exception to a public one.
+     * @param registeredMappings Already registered mappings.
+     * @throws IgniteException If a mapper for the given {@code clazz} already 
registered,
+     *      or {@code clazz} represents Java standard exception like {@link 
NullPointerException}, {@link IllegalArgumentException}.
+     */
+    static void registerMapping(
+            IgniteExceptionMapper<?, ?> mapper,
+            Map<Class<? extends Exception>, IgniteExceptionMapper<?, ?>> 
registeredMappings) {
+        if (registeredMappings.containsKey(mapper.mappingFrom())) {
+            throw new IgniteException(
+                    INTERNAL_ERR,
+                    "Failed to register exception mapper, duplicate found 
[class=" + mapper.mappingFrom().getCanonicalName() + ']');
+        }
+
+        registeredMappings.put(mapper.mappingFrom(), mapper);
+    }
+
+    /**
+     * This method provides a mapping from internal exception to Ignite public 
ones.
+     *
+     * <p>The rules of mapping are the following:</p>
+     * <ul>
+     *     <li>any instance of {@link Error} is returned as is, except {@link 
AssertionError}
+     *     that will always be mapped to {@link IgniteException} with the 
{@link ErrorGroups.Common#INTERNAL_ERR} error code.</li>
+     *     <li>any instance of {@link IgniteException} or {@link 
IgniteCheckedException} is returned as is.</li>
+     *     <li>if there are no any mappers that can do a mapping from the 
given error to a public exception,
+     *     then {@link IgniteException} with the {@link 
ErrorGroups.Common#INTERNAL_ERR} error code is returned.</li>
+     * </ul>
+     *
+     * @param origin Exception to be mapped.
+     * @return Public exception.
+     */
+    public static Throwable mapToPublicException(Throwable origin) {
+        if (origin instanceof Error) {
+            if (origin instanceof AssertionError) {
+                return new IgniteException(INTERNAL_ERR, origin);
+            }
+
+            return origin;
+        }
+
+        if (origin instanceof IgniteException || origin instanceof 
IgniteCheckedException) {
+            return origin;
+        }
+
+        IgniteExceptionMapper<? extends Exception, ? extends Exception> m = 
EXCEPTION_CONVERTERS.get(origin.getClass());
+        if (m != null) {
+            Exception mapped = map(m, origin);
+
+            assert mapped instanceof IgniteException || mapped instanceof 
IgniteCheckedException :
+                    "Unexpected mapping of internal exception to a public one 
[origin=" + origin + ", mapped=" + mapped + ']';
+
+            return mapped;
+        }
+
+        // There are no exception mappings for the given exception. This case 
should be considered as internal error.
+        return new IgniteException(INTERNAL_ERR, origin);
+    }
+
+    /**
+     * Returns a new CompletableFuture that, when the given {@code origin} 
future completes exceptionally,
+     * maps the origin's exception to a public Ignite exception if it is 
needed.
+     *
+     * @param origin The future to use to create a new stage.
+     * @param <T> Type os result.
+     * @return New CompletableFuture.
+     */
+    public static <T> CompletableFuture<T> 
convertToPublicFuture(CompletableFuture<T> origin) {
+        return origin
+                .handle((res, err) -> {
+                    if (err != null) {
+                        throw new 
CompletionException(mapToPublicException(unwrapCause(err)));
+                    }
+
+                    return res;
+                });
+    }
+
+    /**
+     * Returns a new instance of public exception provided by the {@code 
mapper}.
+     *
+     * @param mapper Mapper function to produce a public exception.
+     * @param t Internal exception.
+     * @param <T> Type of an internal exception.
+     * @param <R> Type of a public exception.
+     * @return New public exception.
+     */
+    private static <T extends Exception, R extends Exception> Exception 
map(IgniteExceptionMapper<T, R> mapper, Throwable t) {
+        return mapper.map(mapper.mappingFrom().cast(t));
+    }
+}
diff --git 
a/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMappersProvider.java
 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMappersProvider.java
new file mode 100755
index 0000000000..04ccf97b51
--- /dev/null
+++ 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionMappersProvider.java
@@ -0,0 +1,48 @@
+/*
+ * 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.lang;
+
+import java.util.Collection;
+
+/**
+ * This interface provides the ability to register specific mappers from 
Ignite internal exceptions to a public ones.
+ *
+ * <p>
+ *     Each module can provide such a mapping for its own internal exceptions.
+ *     Designed for integration with {@link java.util.ServiceLoader} 
mechanism, so IgniteExceptionMapper instances
+ *     provided by a library are to be defined as services either
+ *     in {@code 
META-INF/services/org.apache.ignite.lang.IgniteExceptionMapper}, or in a {@code 
module-info.java}.
+ * </p>
+ *
+ * <p>There are the following constraints that should be taken into account by 
a particular implementation of this interface:</p>
+ * <ul>
+ *     <li>it is prohibited to register more than one mapper for the same 
internal exception.</li>
+ *     <li>mapper should only provide mappings either to {@link 
IgniteException}, or {@link IgniteCheckedException}.</li>
+ *     <li>mapper should not provide mapping for Java standard exception like 
{@link NullPointerException},
+ *     {@link IllegalArgumentException}, etc.</li>
+ *     <li>mapper should not provide any mappings for errors {@link 
Error}.</li>
+ * </ul>
+ */
+public interface IgniteExceptionMappersProvider {
+    /**
+     * Returns a collection of mappers to be used to map internal exceptions 
to public ones.
+     *
+     * @return Collection of mappers.
+     */
+    Collection<IgniteExceptionMapper<?, ?>> mappers();
+}
diff --git 
a/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionUtils.java 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionUtils.java
index d24fd192b3..7b447bd70c 100755
--- a/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionUtils.java
+++ b/modules/api/src/main/java/org/apache/ignite/lang/IgniteExceptionUtils.java
@@ -59,7 +59,7 @@ public class IgniteExceptionUtils {
      *         try {
      *             return asyncMethod.join();
      *         } catch (CompletionException e) {
-     *             throw sneakyThrow(createCopyExceptionWithCause(e));
+     *             throw sneakyThrow(copyExceptionWithCause(e));
      *         }
      *     }
      *
@@ -86,7 +86,7 @@ public class IgniteExceptionUtils {
      *         try {
      *             return asyncMethod.get();
      *         } catch (ExecutionException e) {
-     *             throw sneakyThrow(createCopyExceptionWithCause(e));
+     *             throw sneakyThrow(copyExceptionWithCause(e));
      *         }
      *     }
      *
@@ -142,7 +142,7 @@ public class IgniteExceptionUtils {
      * @return Trace identifier.
      */
     public static @Nullable UUID extractTraceIdFrom(Throwable t) {
-        // Perhaps, it would bi nice to introduce a new interface 
IgniteTraceableException to overcome if else statements.
+        // Perhaps, it would be nice to introduce a new interface 
IgniteTraceableException to overcome if else statements.
         if (t instanceof IgniteException) {
             return ((IgniteException) t).traceId();
         } else if (t instanceof IgniteCheckedException) {
@@ -163,7 +163,7 @@ public class IgniteExceptionUtils {
      * @return Trace identifier.
      */
     public static int extractCodeFrom(Throwable t) {
-        // Perhaps, it would bi nice to introduce a new interface 
IgniteTraceableException to overcome if else statements.
+        // Perhaps, it would be nice to introduce a new interface 
IgniteTraceableException to overcome if else statements.
         if (t instanceof IgniteException) {
             return ((IgniteException) t).code();
         } else if (t instanceof IgniteCheckedException) {
@@ -177,7 +177,8 @@ public class IgniteExceptionUtils {
         return INTERNAL_ERR;
     }
 
-    // TODO: https://issues.apache.org/jira/browse/IGNITE-19539 this method 
should be removed or re-worked.
+    // TODO: https://issues.apache.org/jira/browse/IGNITE-19870
+    // This method should be removed or re-worked and usages should be changed 
to IgniteExceptionMapperUtil.mapToPublicException.
     /**
      * Wraps an exception in an IgniteException, extracting trace identifier 
and error code when the specified exception or one of its
      * causes is an IgniteException itself.
@@ -185,6 +186,7 @@ public class IgniteExceptionUtils {
      * @param e Internal exception.
      * @return Public exception.
      */
+    @Deprecated(forRemoval = true)
     public static IgniteException wrap(Throwable e) {
         Objects.requireNonNull(e);
 
diff --git a/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java 
b/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java
index 8629257c60..d2c0979de9 100644
--- a/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java
+++ b/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java
@@ -56,9 +56,9 @@ public interface Transaction {
     CompletableFuture<Void> rollbackAsync();
 
     /**
-     * Returns {code true} if given transaction is a read-only, {@code false 
otherwise}.
+     * Returns {code true} if given transaction is a read-only, {@code false} 
otherwise.
      *
-     * @return {code true} if given transaction is a read-only, {@code false 
otherwise}.
+     * @return {code true} if given transaction is a read-only, {@code false} 
otherwise.
      */
     boolean isReadOnly();
 }
diff --git 
a/modules/api/src/test/java/org/apache/ignite/lang/IgniteExceptionMapperUtilTest.java
 
b/modules/api/src/test/java/org/apache/ignite/lang/IgniteExceptionMapperUtilTest.java
new file mode 100755
index 0000000000..0e8d0015f3
--- /dev/null
+++ 
b/modules/api/src/test/java/org/apache/ignite/lang/IgniteExceptionMapperUtilTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.lang;
+
+import static org.apache.ignite.lang.ErrorGroups.Common.COMMON_ERR_GROUP;
+import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
+import static org.apache.ignite.lang.ErrorGroups.Common.NODE_STOPPING_ERR;
+import static org.apache.ignite.lang.IgniteExceptionMapper.checked;
+import static org.apache.ignite.lang.IgniteExceptionMapper.unchecked;
+import static 
org.apache.ignite.lang.IgniteExceptionMapperUtil.mapToPublicException;
+import static org.apache.ignite.lang.IgniteExceptionMapperUtil.registerMapping;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests mapping internal exceptions to public ones.
+ */
+public class IgniteExceptionMapperUtilTest {
+    /** Internal collection of mappers for tests. */
+    private Map<Class<? extends Exception>, IgniteExceptionMapper<?, ?>> 
mappers = new HashMap<>();
+
+    /**
+     * Tests a simple scenario of registering mapper for internal exceptions.
+     */
+    @Test
+    public void testRegisterBasicMapper() {
+        IgniteExceptionMapper<CustomInternalException, IgniteException> m1 =
+                unchecked(CustomInternalException.class, err -> new 
IgniteException(NODE_STOPPING_ERR, "test"));
+        IgniteExceptionMapper<CustomInternalCheckedException, 
IgniteCheckedException> m2 =
+                checked(CustomInternalCheckedException.class, err -> new 
IgniteCheckedException(NODE_STOPPING_ERR, "test"));
+
+        registerMapping(m1, mappers);
+        registerMapping(m2, mappers);
+
+        assertThat("Failed to register both exception mappers.", 
mappers.size(), is(2));
+    }
+
+    /**
+     * Tests that the only one mapper can be registered for a class.
+     */
+    @Test
+    public void testRegisterDuplicateMapper() {
+        IgniteExceptionMapper<CustomInternalException, IgniteException> m =
+                unchecked(CustomInternalException.class, err -> new 
IgniteException(NODE_STOPPING_ERR, "test"));
+
+        registerMapping(m, mappers);
+
+        IgniteException e = assertThrows(IgniteException.class, () -> 
registerMapping(m, mappers));
+
+        assertThat("Unexpected error group code, it should be 
COMMON_ERR_GROUP.", e.groupCode(), is(COMMON_ERR_GROUP.code()));
+        assertThat("Unexpected error code, it should be INTERNAL_ERR.", 
e.code(), is(INTERNAL_ERR));
+    }
+
+    /**
+     * Tests that {@link AssertionError} is mapped to {@link IgniteException} 
with the {@link ErrorGroups.Common#INTERNAL_ERR}.
+     */
+    @Test
+    public void testAssertionErrorMapping() {
+        Throwable t = mapToPublicException(new AssertionError("test 
assertion"));
+
+        assertThat("AssertionError should be mapped to IgniteException", t, 
isA(IgniteException.class));
+
+        IgniteException mapped = (IgniteException) t;
+
+        assertThat("Unexpected error group code, it should be 
COMMON_ERR_GROUP.", mapped.groupCode(), is(COMMON_ERR_GROUP.code()));
+        assertThat("Unexpected error code, it should be INTERNAL_ERR.", 
mapped.code(), is(INTERNAL_ERR));
+    }
+
+    /**
+     * Tests that {@link AssertionError} is mapped to {@link IgniteException} 
with the {@link ErrorGroups.Common#INTERNAL_ERR}.
+     */
+    @Test
+    public void testErrorMapping() {
+        Throwable t = mapToPublicException(new OutOfMemoryError("test error"));
+
+        assertThat("Error should not be mapped to any exception.", t, 
isA(Error.class));
+    }
+
+    /**
+     * Tests a mapping from Ignite internal exception to a public one.
+     */
+    @Test
+    public void testInternalExceptionMapping() {
+        CustomInternalException internalErr = new CustomInternalException();
+
+        Throwable mappedErr = mapToPublicException(internalErr);
+
+        assertThat("Mapped exception should be an instance of 
IgniteException.", mappedErr, isA(IgniteException.class));
+
+        IgniteException mappedPublicErr = (IgniteException) mappedErr;
+
+        assertThat("Mapped exception should have the same trace identifier.", 
mappedPublicErr.traceId(), is(internalErr.traceId()));
+    }
+
+    /**
+     * Tests a mapping from Ignite internal exception to a public one.
+     */
+    @Test
+    public void testInternalCheckedExceptionMapping() {
+        CustomInternalCheckedException internalErr = new 
CustomInternalCheckedException();
+
+        Throwable mappedErr = mapToPublicException(internalErr);
+
+        assertThat("Mapped exception should be an instance of 
IgniteException.", mappedErr, isA(IgniteCheckedException.class));
+
+        IgniteCheckedException mappedPublicErr = (IgniteCheckedException) 
mappedErr;
+
+        assertThat("Mapped exception should have the same trace identifier.", 
mappedPublicErr.traceId(), is(internalErr.traceId()));
+    }
+
+    /**
+     * Tests that mapping of unregistered type of exception results in {@link 
IgniteException}
+     * with the {@link ErrorGroups.Common#INTERNAL_ERR}.
+     */
+    @Test
+    public void testExceptionMappingOfUnregisteredType() {
+        CustomNoMappingException err = new CustomNoMappingException();
+
+        Throwable t = mapToPublicException(err);
+
+        assertThat("Unregistered exception should be mapped to 
IgniteException", t, isA(IgniteException.class));
+
+        IgniteException mapped = (IgniteException) t;
+
+        assertThat("Unexpected error group code, it should be 
COMMON_ERR_GROUP.", mapped.groupCode(), is(COMMON_ERR_GROUP.code()));
+        assertThat("Unexpected error code, it should be INTERNAL_ERR.", 
mapped.code(), is(INTERNAL_ERR));
+    }
+
+    /**
+     * Test runtime exception.
+     */
+    public static class CustomInternalException extends 
IgniteInternalException {
+        /** Serial version UID. */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * Creates a new instance of CustomInternalException.
+         */
+        public CustomInternalException() {
+            super(INTERNAL_ERR, "Test internal exception.");
+        }
+    }
+
+    /**
+     * Test checked exception.
+     */
+    public static class CustomInternalCheckedException extends 
IgniteInternalCheckedException {
+        /** Serial version UID. */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * Creates a new instance of CustomInternalCheckedException.
+         */
+        public CustomInternalCheckedException() {
+            super(INTERNAL_ERR, "Test internal checked exception.");
+        }
+
+        /**
+         * Creates a new instance of CustomInternalCheckedException.
+         *
+         * @param traceId Trace identifier.
+         */
+        public CustomInternalCheckedException(UUID traceId) {
+            super(traceId, INTERNAL_ERR, "Test internal checked exception.");
+        }
+    }
+
+    /**
+     * Test exception.
+     */
+    public static class CustomNoMappingException extends 
IgniteInternalException {
+        /** Serial version UID. */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * Creates a new instance of CustomNoMappingException.
+         */
+        public CustomNoMappingException() {
+            super(INTERNAL_ERR, "Test internal exception [err=no mapping]");
+        }
+    }
+}
diff --git 
a/modules/api/src/test/java/org/apache/ignite/lang/TestIgniteExceptionMappersProvider.java
 
b/modules/api/src/test/java/org/apache/ignite/lang/TestIgniteExceptionMappersProvider.java
new file mode 100755
index 0000000000..6b022ca88f
--- /dev/null
+++ 
b/modules/api/src/test/java/org/apache/ignite/lang/TestIgniteExceptionMappersProvider.java
@@ -0,0 +1,52 @@
+/*
+ * 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.lang;
+
+import static org.apache.ignite.lang.ErrorGroups.Common.NODE_STOPPING_ERR;
+import static org.apache.ignite.lang.IgniteExceptionMapper.checked;
+import static org.apache.ignite.lang.IgniteExceptionMapper.unchecked;
+
+import com.google.auto.service.AutoService;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import 
org.apache.ignite.lang.IgniteExceptionMapperUtilTest.CustomInternalCheckedException;
+import 
org.apache.ignite.lang.IgniteExceptionMapperUtilTest.CustomInternalException;
+
+/**
+ * This class represents a test exception mapper.
+ */
+@AutoService(IgniteExceptionMappersProvider.class)
+public class TestIgniteExceptionMappersProvider implements 
IgniteExceptionMappersProvider {
+    @Override
+    public Collection<IgniteExceptionMapper<?, ?>> mappers() {
+        List<IgniteExceptionMapper<?, ?>> mappers = new ArrayList<>();
+
+        mappers.add(
+                unchecked(
+                        CustomInternalException.class,
+                        err -> new IgniteException(err.traceId(), 
NODE_STOPPING_ERR, err)));
+
+        mappers.add(
+                checked(
+                        CustomInternalCheckedException.class,
+                        err -> new IgniteCheckedException(err.traceId(), 
NODE_STOPPING_ERR, err)));
+
+        return mappers;
+    }
+}
diff --git 
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
index fce71348b0..a82ecd1253 100644
--- 
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
+++ 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
@@ -42,7 +42,7 @@ import org.apache.ignite.configuration.validation.Validator;
  *     <li><b>rootKeys</b> ({@link RootKey} instances)</li>
  *     <li><b>validators</b> ({@link Validator} instances)</li>
  *     <li><b>internalSchemaExtensions</b> (classes annotated with {@link 
InternalConfiguration})</li>
- *     <li><b>polymorphicSchemaExtensions</b> (classes annotataed with {@link 
PolymorphicConfig})</li>
+ *     <li><b>polymorphicSchemaExtensions</b> (classes annotated with {@link 
PolymorphicConfig})</li>
  * </ul>
  *
  * @see ConfigurationType

Reply via email to