Skip to content

Commit

Permalink
Add annotation ReadPreference.
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgerod committed Sep 14, 2023
1 parent 11b992c commit 466f869
Show file tree
Hide file tree
Showing 11 changed files with 418 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @author Jorge Rodríguez
*/
@Collation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@QueryAnnotation
@Hint
@ReadPreference
public @interface Query {

/**
Expand Down Expand Up @@ -147,4 +149,21 @@
*/
@AliasFor(annotation = Hint.class, attribute = "indexName")
String hint() default "";

/**
* The mode of the read preference to use. <br />
* {@code @Query(value = "...", readPreference = "secondary")} can be used as shortcut for:
*
* <pre class="code">
* &#64;Query(...)
* &#64;ReadPreference("secondary")
* List&lt;User&gt; findAllByLastname(String collation);
* </pre>
*
* @return the index name.
* @since 4.2
* @see ReadPreference#value()
*/
@AliasFor(annotation = ReadPreference.class, attribute = "value")
String readPreference() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2011-2023 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.repository;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to declare read preference for repository and query.
*
* @author Jorge Rodríguez
* @since 4.2
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface ReadPreference {

/**
* Configure read preference mode
* @return read preference mode
*/
String value() default "";

/**
* Set read preference tags
* @return read preference tags
*/
ReadPreferenceTag[] tags() default {};

/**
* Set read preference maxStalenessSeconds
* @return read preference maxStalenessSeconds
*/
long maxStalenessSeconds() default -1;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2011-2023 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.repository;

/**
* Annotation used by {@link ReadPreference} for define {@link com.mongodb.Tag}
*
* @author Jorge Rodríguez
* @since 4.2
*/
public @interface ReadPreferenceTag {

/**
* Set the name of tag
* @return name of tag
*/
String name();

/**
* Set the value of tag
* @return value of tag
*/
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @author Jorge Rodríguez
*/
public abstract class AbstractMongoQuery implements RepositoryQuery {

Expand Down Expand Up @@ -137,6 +138,7 @@ protected Object doExecute(MongoQueryMethod method, ResultProcessor processor, C
query = applyAnnotatedDefaultSortIfPresent(query);
query = applyAnnotatedCollationIfPresent(query, accessor);
query = applyHintIfPresent(query);
query = applyAnnotatedReadPreferenceIfPresent(query);

FindWithQuery<?> find = typeToRead == null //
? executableFind //
Expand All @@ -145,6 +147,22 @@ protected Object doExecute(MongoQueryMethod method, ResultProcessor processor, C
return getExecution(accessor, find).execute(query);
}

/**
* If present apply the {@link com.mongodb.ReadPreference} from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
*
* @param query must not be {@literal null}.
* @return never {@literal null}.
* @since 4.2
*/
private Query applyAnnotatedReadPreferenceIfPresent(Query query) {

if (!method.hasAnnotatedReadPreference()) {
return query;
}

return query.withReadPreference(method.getAnnotatedReadPreference());
}

private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, FindWithQuery<?> operation) {

if (isDeleteQuery()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Jorge Rodríguez
* @since 2.0
*/
public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
Expand Down Expand Up @@ -161,6 +162,8 @@ protected Publisher<Object> doExecute(ReactiveMongoQueryMethod method, ResultPro
query = applyAnnotatedDefaultSortIfPresent(query);
query = applyAnnotatedCollationIfPresent(query, accessor);
query = applyHintIfPresent(query);
query = applyAnnotatedReadPreferenceIfPresent(query);


FindWithQuery<?> find = typeToRead == null //
? findOperationWithProjection //
Expand Down Expand Up @@ -229,6 +232,7 @@ private boolean isTailable(MongoQueryMethod method) {
return method.getTailableAnnotation() != null;
}


Query applyQueryMetaAttributesWhenPresent(Query query) {

if (method.hasQueryMetaAttributes()) {
Expand Down Expand Up @@ -286,6 +290,22 @@ Query applyHintIfPresent(Query query) {
return query.withHint(method.getAnnotatedHint());
}

/**
* If present apply the {@link com.mongodb.ReadPreference} from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
*
* @param query must not be {@literal null}.
* @return never {@literal null}.
* @since 4.2
*/
private Query applyAnnotatedReadPreferenceIfPresent(Query query) {

if (!method.hasAnnotatedReadPreference()) {
return query;
}

return query.withReadPreference(method.getAnnotatedReadPreference());
}

/**
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.mongodb.Tag;
import com.mongodb.TagSet;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.geo.GeoPage;
import org.springframework.data.geo.GeoResult;
Expand All @@ -36,6 +40,7 @@
import org.springframework.data.mongodb.repository.Hint;
import org.springframework.data.mongodb.repository.Meta;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReadPreference;
import org.springframework.data.mongodb.repository.Tailable;
import org.springframework.data.mongodb.repository.Update;
import org.springframework.data.projection.ProjectionFactory;
Expand All @@ -57,6 +62,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @author Jorge Rodríguez
*/
public class MongoQueryMethod extends QueryMethod {

Expand Down Expand Up @@ -314,6 +320,58 @@ public String getAnnotatedSort() {
"Expected to find @Query annotation but did not; Make sure to check hasAnnotatedSort() before."));
}


/**
* Check if the query method is decorated with an non empty {@link Query#collation()}.
*
* @return true if method annotated with {@link Query} or {@link Aggregation} having a non-empty collation attribute.
* @since 4.2
*/
public boolean hasAnnotatedReadPreference() {
return doFindReadPreferenceAnnotation().map(ReadPreference::value).filter(StringUtils::hasText).isPresent();
}

/**
* Get the {@link com.mongodb.ReadPreference} extracted from the {@link ReadPreference} annotation.
*
* @return the {@link ReadPreference()}.
* @throws IllegalStateException if method not annotated with {@link Query}. Make sure to check
* {@link #hasAnnotatedQuery()} first.
* @since 4.2
*/
public com.mongodb.ReadPreference getAnnotatedReadPreference() {

return doFindReadPreferenceAnnotation().map(annotationReadPreference -> {

com.mongodb.ReadPreference readPreference = com.mongodb.ReadPreference.valueOf(annotationReadPreference.value());

if (annotationReadPreference.tags().length > 0) {
List<Tag> tags = Arrays.stream(annotationReadPreference.tags())
.map(tag -> new Tag(tag.name(), tag.value()))
.collect(Collectors.toList());
readPreference = readPreference.withTagSet(new TagSet(tags));
}

if (annotationReadPreference.maxStalenessSeconds() > 0) {
readPreference = readPreference.withMaxStalenessMS(annotationReadPreference.maxStalenessSeconds(), TimeUnit.SECONDS);
}

return readPreference;
}).orElseThrow(() -> new IllegalStateException(
"Expected to find @ReadPreference annotation but did not; Make sure to check hasAnnotatedReadPreference() before."));
}

/**
* Get {@link com.mongodb.ReadPreference} from query. First check if the method is annotated. If not, check if the class is annotated.
* So if the method and the class are annotated with @ReadPreference, the method annotation takes precedence.
* @return the {@link com.mongodb.ReadPreference}
* @since 4.2
*/
private Optional<ReadPreference> doFindReadPreferenceAnnotation() {
return doFindAnnotation(ReadPreference.class).or(() -> doFindAnnotationInClass(ReadPreference.class));
}


/**
* Check if the query method is decorated with an non empty {@link Query#collation()} or or
* {@link Aggregation#collation()}.
Expand Down Expand Up @@ -400,11 +458,21 @@ Optional<Update> lookupUpdateAnnotation() {

@SuppressWarnings("unchecked")
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {


return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
}

@SuppressWarnings("unchecked")
private <A extends Annotation> Optional<A> doFindAnnotationInClass(Class<A> annotationType) {

Optional<Annotation> mergedAnnotation = Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), annotationType));
annotationCache.put(annotationType, mergedAnnotation);

return (Optional<A>) mergedAnnotation;
}

@Override
public boolean isModifyingQuery() {
return isModifying.get();
Expand Down
Loading

0 comments on commit 466f869

Please sign in to comment.