Skip to content

Commit dced760

Browse files
christophstroblmp911de
authored andcommitted
Refine exception translation for client session exceptions.
Closes spring-projects#3148 See spring-projects#2939 Original Pull Request: spring-projects#604
1 parent d04e76f commit dced760

File tree

7 files changed

+289
-38
lines changed

7 files changed

+289
-38
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb;
17+
18+
import org.springframework.dao.TransientDataAccessException;
19+
import org.springframework.lang.Nullable;
20+
21+
/**
22+
* {@link TransientDataAccessException} specific to MongoDB {@link com.mongodb.session.ClientSession} related data
23+
* access failures such as reading data using an already closed session.
24+
*
25+
* @author Christoph Strobl
26+
* @since 3.3
27+
*/
28+
public class TransientClientSessionException extends TransientMongoDbException {
29+
30+
/**
31+
* Constructor for {@link TransientClientSessionException}.
32+
*
33+
* @param msg the detail message. Can be {@literal null}.
34+
* @param cause the root cause. Can be {@literal null}.
35+
*/
36+
public TransientClientSessionException(@Nullable String msg, @Nullable Throwable cause) {
37+
super(msg, cause);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb;
17+
18+
import org.springframework.dao.TransientDataAccessException;
19+
import org.springframework.lang.Nullable;
20+
21+
/**
22+
* Root of the hierarchy of MongoDB specific data access exceptions that are considered transient such as
23+
* {@link com.mongodb.MongoException MongoExceptions} carrying {@link com.mongodb.MongoException#hasErrorLabel(String)
24+
* specific labels}.
25+
*
26+
* @author Christoph Strobl
27+
* @since 3.3
28+
*/
29+
public class TransientMongoDbException extends TransientDataAccessException {
30+
31+
/**
32+
* Constructor for {@link TransientMongoDbException}.
33+
*
34+
* @param msg the detail message. Can be {@literal null}.
35+
* @param cause the root cause. Can be {@literal null}.
36+
*/
37+
public TransientMongoDbException(String msg, @Nullable Throwable cause) {
38+
super(msg, cause);
39+
}
40+
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java

+62-15
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
import org.springframework.dao.InvalidDataAccessApiUsageException;
2626
import org.springframework.dao.InvalidDataAccessResourceUsageException;
2727
import org.springframework.dao.PermissionDeniedDataAccessException;
28+
import org.springframework.dao.TransientDataAccessException;
2829
import org.springframework.dao.support.PersistenceExceptionTranslator;
2930
import org.springframework.data.mongodb.ClientSessionException;
30-
import org.springframework.data.mongodb.MongoTransactionException;
31+
import org.springframework.data.mongodb.TransientClientSessionException;
32+
import org.springframework.data.mongodb.TransientMongoDbException;
3133
import org.springframework.data.mongodb.UncategorizedMongoDbException;
3234
import org.springframework.data.mongodb.util.MongoDbErrorCodes;
3335
import org.springframework.lang.Nullable;
@@ -65,9 +67,26 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
6567

6668
private static final Set<String> SECURITY_EXCEPTIONS = Set.of("MongoCryptException");
6769

70+
@Override
6871
@Nullable
6972
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
7073

74+
DataAccessException translatedException = doTranslateException(ex);
75+
if (translatedException == null) {
76+
return null;
77+
}
78+
79+
// Translated exceptions that per se are not be recoverable (eg. WriteConflicts), might still be transient inside a
80+
// transaction. Let's wrap those.
81+
return (isTransientFailure(ex) && !(translatedException instanceof TransientDataAccessException))
82+
? new TransientMongoDbException(ex.getMessage(), translatedException)
83+
: translatedException;
84+
85+
}
86+
87+
@Nullable
88+
DataAccessException doTranslateException(RuntimeException ex) {
89+
7190
// Check for well-known MongoException subclasses.
7291

7392
if (ex instanceof BsonInvalidOperationException) {
@@ -94,13 +113,13 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
94113

95114
if (DATA_INTEGRITY_EXCEPTIONS.contains(exception)) {
96115

97-
if (ex instanceof MongoServerException mse) {
98-
if (mse.getCode() == 11000) {
116+
if (ex instanceof MongoServerException) {
117+
if (MongoDbErrorCodes.isDataDuplicateKeyError(ex)) {
99118
return new DuplicateKeyException(ex.getMessage(), ex);
100119
}
101120
if (ex instanceof MongoBulkWriteException bulkException) {
102-
for (BulkWriteError x : bulkException.getWriteErrors()) {
103-
if (x.getCode() == 11000) {
121+
for (BulkWriteError writeError : bulkException.getWriteErrors()) {
122+
if (MongoDbErrorCodes.isDuplicateKeyCode(writeError.getCode())) {
104123
return new DuplicateKeyException(ex.getMessage(), ex);
105124
}
106125
}
@@ -115,20 +134,27 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
115134

116135
int code = mongoException.getCode();
117136

118-
if (MongoDbErrorCodes.isDuplicateKeyCode(code)) {
137+
if (MongoDbErrorCodes.isDuplicateKeyError(mongoException)) {
119138
return new DuplicateKeyException(ex.getMessage(), ex);
120-
} else if (MongoDbErrorCodes.isDataAccessResourceFailureCode(code)) {
139+
}
140+
if (MongoDbErrorCodes.isDataAccessResourceError(mongoException)) {
121141
return new DataAccessResourceFailureException(ex.getMessage(), ex);
122-
} else if (MongoDbErrorCodes.isInvalidDataAccessApiUsageCode(code) || code == 10003 || code == 12001
123-
|| code == 12010 || code == 12011 || code == 12012) {
142+
}
143+
if (MongoDbErrorCodes.isInvalidDataAccessApiUsageError(mongoException) || code == 12001 || code == 12010
144+
|| code == 12011 || code == 12012) {
124145
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
125-
} else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) {
146+
}
147+
if (MongoDbErrorCodes.isPermissionDeniedError(mongoException)) {
126148
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
127-
} else if (MongoDbErrorCodes.isClientSessionFailureCode(code)) {
128-
return new ClientSessionException(ex.getMessage(), ex);
129-
} else if (MongoDbErrorCodes.isTransactionFailureCode(code)) {
130-
return new MongoTransactionException(ex.getMessage(), ex);
131-
} else if(ex.getCause() != null && SECURITY_EXCEPTIONS.contains(ClassUtils.getShortName(ex.getCause().getClass()))) {
149+
}
150+
if (MongoDbErrorCodes.isDataIntegrityViolationError(mongoException)) {
151+
return new DataIntegrityViolationException(mongoException.getMessage(), mongoException);
152+
}
153+
if (MongoDbErrorCodes.isClientSessionFailure(mongoException)) {
154+
return isTransientFailure(mongoException) ? new TransientClientSessionException(ex.getMessage(), ex)
155+
: new ClientSessionException(ex.getMessage(), ex);
156+
}
157+
if (ex.getCause() != null && SECURITY_EXCEPTIONS.contains(ClassUtils.getShortName(ex.getCause().getClass()))) {
132158
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
133159
}
134160

@@ -150,4 +176,25 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
150176
// that translation should not occur.
151177
return null;
152178
}
179+
180+
/**
181+
* Check if a given exception holds an error label indicating a transient failure.
182+
*
183+
* @param e
184+
* @return {@literal true} if the given {@link Exception} is a {@link MongoException} holding one of the transient
185+
* exception error labels.
186+
* @see MongoException#hasErrorLabel(String)
187+
* @since 3.3
188+
*/
189+
public static boolean isTransientFailure(Exception e) {
190+
191+
if (!(e instanceof MongoException)) {
192+
return false;
193+
}
194+
195+
MongoException mongoException = (MongoException) e;
196+
197+
return mongoException.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)
198+
|| mongoException.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
199+
}
153200
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.springframework.data.mapping.context.MappingContext;
2929
import org.springframework.data.mapping.context.MappingContextEvent;
3030
import org.springframework.data.mongodb.MongoDatabaseFactory;
31-
import org.springframework.data.mongodb.UncategorizedMongoDbException;
3231
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
3332
import org.springframework.data.mongodb.core.mapping.Document;
3433
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -152,7 +151,7 @@ void createIndex(IndexDefinitionHolder indexDefinition) {
152151
IndexOperations indexOperations = indexOperationsProvider.indexOps(indexDefinition.getCollection());
153152
indexOperations.ensureIndex(indexDefinition);
154153

155-
} catch (UncategorizedMongoDbException ex) {
154+
} catch (DataIntegrityViolationException ex) {
156155

157156
if (ex.getCause() instanceof MongoException mongoException
158157
&& MongoDbErrorCodes.isDataIntegrityViolationCode(mongoException.getCode())) {

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import org.apache.commons.logging.Log;
2222
import org.apache.commons.logging.LogFactory;
2323
import org.springframework.core.annotation.AnnotatedElementUtils;
24+
import org.springframework.dao.DataIntegrityViolationException;
2425
import org.springframework.data.domain.Sort;
2526
import org.springframework.data.domain.Sort.Direction;
2627
import org.springframework.data.domain.Sort.Order;
27-
import org.springframework.data.mongodb.UncategorizedMongoDbException;
2828
import org.springframework.data.mongodb.core.MongoOperations;
2929
import org.springframework.data.mongodb.core.index.Index;
3030
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
@@ -111,7 +111,7 @@ public void onCreation(PartTreeMongoQuery query) {
111111
MongoEntityMetadata<?> metadata = query.getQueryMethod().getEntityInformation();
112112
try {
113113
indexOperationsProvider.indexOps(metadata.getCollectionName(), metadata.getJavaType()).ensureIndex(index);
114-
} catch (UncategorizedMongoDbException e) {
114+
} catch (DataIntegrityViolationException e) {
115115

116116
if (e.getCause() instanceof MongoException mongoException) {
117117

0 commit comments

Comments
 (0)