MongoTransactionOptionsResolver.java
/*
* Copyright 2024-present 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 java.util.Map;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link TransactionOptionResolver} reading MongoDB specific {@link MongoTransactionOptions transaction options} from
* a {@link TransactionDefinition}. Implementations of {@link MongoTransactionOptions} may choose a specific
* {@link #getLabelPrefix() prefix} for {@link TransactionAttribute#getLabels() transaction attribute labels} to avoid
* evaluating non-store specific ones.
* <p>
* {@link TransactionAttribute#getLabels()} evaluated by default should follow the property style using {@code =} to
* separate key and value pairs.
* <p>
* By default {@link #resolve(TransactionDefinition)} will filter labels by the {@link #getLabelPrefix() prefix} and
* strip the prefix from the label before handing the pruned {@link Map} to the {@link #convert(Map)} function.
* <p>
* A transaction definition with labels targeting MongoDB may look like the following:
* <p>
* <code class="java">
* @Transactional(label = { "mongo:readConcern=majority" })
* </code>
*
* @author Christoph Strobl
* @since 4.3
*/
public interface MongoTransactionOptionsResolver extends TransactionOptionResolver<MongoTransactionOptions> {
/**
* Obtain the default {@link MongoTransactionOptionsResolver} implementation using a {@literal mongo:}
* {@link #getLabelPrefix() prefix}.
*
* @return instance of default {@link MongoTransactionOptionsResolver} implementation.
*/
static MongoTransactionOptionsResolver defaultResolver() {
return DefaultMongoTransactionOptionsResolver.INSTANCE;
}
/**
* Get the prefix used to filter applicable {@link TransactionAttribute#getLabels() labels}.
*
* @return {@literal null} if no label defined.
*/
@Nullable
String getLabelPrefix();
/**
* Resolve {@link MongoTransactionOptions} from a given {@link TransactionDefinition} by evaluating
* {@link TransactionAttribute#getLabels()} labels if possible.
* <p>
* Splits applicable labels property style using {@literal =} as deliminator and removes a potential
* {@link #getLabelPrefix() prefix} before calling {@link #convert(Map)} with filtered label values.
*
* @param definition
* @return {@link MongoTransactionOptions#NONE} in case the given {@link TransactionDefinition} is not a
* {@link TransactionAttribute} if no matching {@link TransactionAttribute#getLabels() labels} could be found.
* @throws IllegalArgumentException for options that do not map to valid transactions options or malformatted labels.
*/
@Override
default MongoTransactionOptions resolve(TransactionDefinition definition) {
if (!(definition instanceof TransactionAttribute attribute)) {
return MongoTransactionOptions.NONE;
}
if (attribute.getLabels().isEmpty()) {
return MongoTransactionOptions.NONE;
}
Map<String, String> attributeMap = attribute.getLabels().stream()
.filter(it -> !StringUtils.hasText(getLabelPrefix()) || it.startsWith(getLabelPrefix()))
.map(it -> StringUtils.hasText(getLabelPrefix()) ? it.substring(getLabelPrefix().length()) : it).map(it -> {
String[] kvPair = StringUtils.split(it, "=");
Assert.isTrue(kvPair != null && kvPair.length == 2,
() -> "No value present for transaction option %s".formatted(kvPair != null ? kvPair[0] : it));
return kvPair;
})
.collect(Collectors.toMap(it -> it[0].trim(), it -> it[1].trim()));
return attributeMap.isEmpty() ? MongoTransactionOptions.NONE : convert(attributeMap);
}
/**
* Convert the given {@link Map} into an instance of {@link MongoTransactionOptions}.
*
* @param options never {@literal null}.
* @return never {@literal null}.
* @throws IllegalArgumentException for invalid options.
*/
MongoTransactionOptions convert(Map<String, String> options);
}