Skip to content

Refine exception translation for client session exceptions #605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.0-DATAMONGO-2073-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.0-DATAMONGO-2073-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.0-DATAMONGO-2073-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.0-DATAMONGO-2073-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.mongodb;

import org.springframework.dao.TransientDataAccessException;

/**
* {@link TransientDataAccessException} specific to MongoDB {@link com.mongodb.session.ClientSession} related data
* access failures such as reading data using an already closed session.
*
* @author Christoph Strobl
* @since 4.4
*/
public class TransientClientSessionException extends TransientMongoDbException {

/**
* Constructor for {@link TransientClientSessionException}.
*
* @param msg the detail message.
* @param cause the root cause.
*/
public TransientClientSessionException(String msg, Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.mongodb;

import org.springframework.dao.TransientDataAccessException;

/**
* Root of the hierarchy of MongoDB specific data access exceptions that are considered transient such as
* {@link com.mongodb.MongoException MongoExceptions} carrying {@link com.mongodb.MongoException#hasErrorLabel(String)
* specific labels}.
*
* @author Christoph Strobl
* @since 4.4
*/
public class TransientMongoDbException extends TransientDataAccessException {

/**
* Constructor for {@link TransientMongoDbException}.
*
* @param msg the detail message.
* @param cause the root cause.
*/
public TransientMongoDbException(String msg, Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,14 @@
*/
public class MongoClientFactoryBean extends AbstractFactoryBean<MongoClient> implements PersistenceExceptionTranslator {

private static final PersistenceExceptionTranslator DEFAULT_EXCEPTION_TRANSLATOR = new MongoExceptionTranslator();

private @Nullable MongoClientSettings mongoClientSettings;
private @Nullable String host;
private @Nullable Integer port;
private @Nullable List<MongoCredential> credential = null;
private @Nullable ConnectionString connectionString;
private @Nullable String replicaSet = null;

private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_EXCEPTION_TRANSLATOR;
private PersistenceExceptionTranslator exceptionTranslator = MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR;

/**
* Set the {@link MongoClientSettings} to be used when creating {@link MongoClient}.
Expand Down Expand Up @@ -116,23 +114,34 @@ public void setReplicaSet(@Nullable String replicaSet) {
* @param exceptionTranslator
*/
public void setExceptionTranslator(@Nullable PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator == null ? DEFAULT_EXCEPTION_TRANSLATOR : exceptionTranslator;
}

public Class<? extends MongoClient> getObjectType() {
return MongoClient.class;
this.exceptionTranslator = exceptionTranslator == null ? MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR
: exceptionTranslator;
}

@Override
@Nullable
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}

@Override
public Class<? extends MongoClient> getObjectType() {
return MongoClient.class;
}

@Override
protected MongoClient createInstance() throws Exception {
return createMongoClient(computeClientSetting());
}

@Override
protected void destroyInstance(@Nullable MongoClient instance) throws Exception {

if (instance != null) {
instance.close();
}
}

/**
* Create {@link MongoClientSettings} based on configuration and priority (lower is better).
* <ol>
Expand Down Expand Up @@ -324,14 +333,6 @@ private <T> T computeSettingsValue(T defaultValue, T fromSettings, T fromConnect
return !fromConnectionStringIsDefault ? fromConnectionString : defaultValue;
}

@Override
protected void destroyInstance(@Nullable MongoClient instance) throws Exception {

if (instance != null) {
instance.close();
}
}

private MongoClient createMongoClient(MongoClientSettings settings) throws UnknownHostException {
return MongoClients.create(settings, SpringDataMongoDB.driverInformation());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@

/**
* Common base class for usage with both {@link com.mongodb.client.MongoClients} defining common properties such as
* database name and exception translator.
* <br />
* database name and exception translator. <br />
* Not intended to be used directly.
*
* @author Christoph Strobl
Expand All @@ -47,8 +46,8 @@ public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFac
private final C mongoClient;
private final String databaseName;
private final boolean mongoInstanceCreated;
private final PersistenceExceptionTranslator exceptionTranslator;

private PersistenceExceptionTranslator exceptionTranslator;
private @Nullable WriteConcern writeConcern;

/**
Expand All @@ -75,15 +74,31 @@ protected MongoDatabaseFactorySupport(C mongoClient, String databaseName, boolea
this.exceptionTranslator = exceptionTranslator;
}

/**
* Configures the {@link PersistenceExceptionTranslator} to be used.
*
* @param exceptionTranslator the exception translator to set.
* @since 4.4
*/
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}

@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}

/**
* Configures the {@link WriteConcern} to be used on the {@link MongoDatabase} instance being created.
*
* @param writeConcern the writeConcern to set
* @param writeConcern the writeConcern to set.
*/
public void setWriteConcern(WriteConcern writeConcern) {
this.writeConcern = writeConcern;
}

@Override
public MongoDatabase getMongoDatabase() throws DataAccessException {
return getMongoDatabase(getDefaultDatabaseName());
}
Expand Down Expand Up @@ -116,10 +131,7 @@ public void destroy() throws Exception {
}
}

public PersistenceExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}

@Override
public MongoDatabaseFactory withSession(ClientSession session) {
return new MongoDatabaseFactorySupport.ClientSessionBoundMongoDbFactory(session, this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Set;

import org.bson.BsonInvalidOperationException;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
Expand All @@ -27,7 +28,7 @@
import org.springframework.dao.PermissionDeniedDataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.ClientSessionException;
import org.springframework.data.mongodb.MongoTransactionException;
import org.springframework.data.mongodb.TransientClientSessionException;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.util.MongoDbErrorCodes;
import org.springframework.lang.Nullable;
Expand All @@ -51,6 +52,8 @@
*/
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {

public static final MongoExceptionTranslator DEFAULT_EXCEPTION_TRANSLATOR = new MongoExceptionTranslator();

private static final Set<String> DUPLICATE_KEY_EXCEPTIONS = Set.of("MongoException.DuplicateKey",
"DuplicateKeyException");

Expand All @@ -65,8 +68,14 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator

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

@Override
@Nullable
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return doTranslateException(ex);
}

@Nullable
DataAccessException doTranslateException(RuntimeException ex) {

// Check for well-known MongoException subclasses.

Expand Down Expand Up @@ -94,13 +103,13 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {

if (DATA_INTEGRITY_EXCEPTIONS.contains(exception)) {

if (ex instanceof MongoServerException mse) {
if (mse.getCode() == 11000) {
if (ex instanceof MongoServerException) {
if (MongoDbErrorCodes.isDataDuplicateKeyError(ex)) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
if (ex instanceof MongoBulkWriteException bulkException) {
for (BulkWriteError x : bulkException.getWriteErrors()) {
if (x.getCode() == 11000) {
for (BulkWriteError writeError : bulkException.getWriteErrors()) {
if (MongoDbErrorCodes.isDuplicateKeyCode(writeError.getCode())) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
}
Expand All @@ -115,20 +124,34 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {

int code = mongoException.getCode();

if (MongoDbErrorCodes.isDuplicateKeyCode(code)) {
if (MongoDbErrorCodes.isDuplicateKeyError(mongoException)) {
return new DuplicateKeyException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isDataAccessResourceFailureCode(code)) {
}
if (MongoDbErrorCodes.isDataAccessResourceError(mongoException)) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isInvalidDataAccessApiUsageCode(code) || code == 10003 || code == 12001
|| code == 12010 || code == 12011 || code == 12012) {
}
if (MongoDbErrorCodes.isInvalidDataAccessApiUsageError(mongoException) || code == 12001 || code == 12010
|| code == 12011 || code == 12012) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) {
}
if (MongoDbErrorCodes.isPermissionDeniedError(mongoException)) {
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isClientSessionFailureCode(code)) {
return new ClientSessionException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isTransactionFailureCode(code)) {
return new MongoTransactionException(ex.getMessage(), ex);
} else if(ex.getCause() != null && SECURITY_EXCEPTIONS.contains(ClassUtils.getShortName(ex.getCause().getClass()))) {
}
if (MongoDbErrorCodes.isDataIntegrityViolationError(mongoException)) {
return new DataIntegrityViolationException(mongoException.getMessage(), mongoException);
}
if (MongoDbErrorCodes.isClientSessionFailure(mongoException)) {
return isTransientFailure(mongoException) ? new TransientClientSessionException(ex.getMessage(), ex)
: new ClientSessionException(ex.getMessage(), ex);
}
if (MongoDbErrorCodes.isDataIntegrityViolationError(mongoException)) {
return new DataIntegrityViolationException(mongoException.getMessage(), mongoException);
}
if (MongoDbErrorCodes.isClientSessionFailure(mongoException)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as in line 143?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the DataIntegrityViolation block is duplicated as well, seems a remainder from conflict resolution. Both if branches can go away.

return isTransientFailure(mongoException) ? new TransientClientSessionException(ex.getMessage(), ex)
: new ClientSessionException(ex.getMessage(), ex);
}
if (ex.getCause() != null && SECURITY_EXCEPTIONS.contains(ClassUtils.getShortName(ex.getCause().getClass()))) {
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
}

Expand All @@ -150,4 +173,23 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
// that translation should not occur.
return null;
}

/**
* Check if a given exception holds an error label indicating a transient failure.
*
* @param e the exception to inspect.
* @return {@literal true} if the given {@link Exception} is a {@link MongoException} holding one of the transient
* exception error labels.
* @see MongoException#hasErrorLabel(String)
* @since 4.4
*/
public boolean isTransientFailure(Exception e) {

if (!(e instanceof MongoException mongoException)) {
return false;
}

return mongoException.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)
|| mongoException.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
}
}
Loading