Skip to content

Commit b233c8f

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-2073 - Evaluate exception label when translating MongoExceptions.
We now distinguish between Transient and NonTransient failures by checking the Error labels of an Error and create the according DataAccessException based on that information. These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended. * [ ] http://www.apache.org/licenses/ with 1 occurrences migrated to: https://www.apache.org/licenses/ ([https](https://www.apache.org/licenses/) result 200). * [ ] http://www.apache.org/licenses/LICENSE-2.0 with 852 occurrences migrated to: https://www.apache.org/licenses/LICENSE-2.0 ([https](https://www.apache.org/licenses/LICENSE-2.0) result 200). Original Pull Request: #721
1 parent 422a9ca commit b233c8f

File tree

7 files changed

+296
-38
lines changed

7 files changed

+296
-38
lines changed
Lines changed: 39 additions & 0 deletions
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+
}
Lines changed: 40 additions & 0 deletions
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+
}

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

Lines changed: 69 additions & 15 deletions
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,34 @@ 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 (MongoDbErrorCodes.isDataIntegrityViolationError(mongoException)) {
158+
return new DataIntegrityViolationException(mongoException.getMessage(), mongoException);
159+
}
160+
if (MongoDbErrorCodes.isClientSessionFailure(mongoException)) {
161+
return isTransientFailure(mongoException) ? new TransientClientSessionException(ex.getMessage(), ex)
162+
: new ClientSessionException(ex.getMessage(), ex);
163+
}
164+
if (ex.getCause() != null && SECURITY_EXCEPTIONS.contains(ClassUtils.getShortName(ex.getCause().getClass()))) {
132165
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
133166
}
134167

@@ -150,4 +183,25 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
150183
// that translation should not occur.
151184
return null;
152185
}
186+
187+
/**
188+
* Check if a given exception holds an error label indicating a transient failure.
189+
*
190+
* @param e
191+
* @return {@literal true} if the given {@link Exception} is a {@link MongoException} holding one of the transient
192+
* exception error labels.
193+
* @see MongoException#hasErrorLabel(String)
194+
* @since 3.3
195+
*/
196+
public static boolean isTransientFailure(Exception e) {
197+
198+
if (!(e instanceof MongoException)) {
199+
return false;
200+
}
201+
202+
MongoException mongoException = (MongoException) e;
203+
204+
return mongoException.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)
205+
|| mongoException.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
206+
}
153207
}

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

Lines changed: 1 addition & 2 deletions
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())) {

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

Lines changed: 2 additions & 2 deletions
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)