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 <[email protected]>
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