Compare commits

..

13 Commits

Author SHA1 Message Date
Mark Paluch
b8f093269d DATAMONGO-2007 - Release version 2.0.9 (Kay SR9). 2018-07-26 14:44:00 +02:00
Mark Paluch
172db96fea DATAMONGO-2007 - Prepare 2.0.9 (Kay SR9). 2018-07-26 14:43:06 +02:00
Mark Paluch
c8381c734b DATAMONGO-2007 - Updated changelog. 2018-07-26 14:42:54 +02:00
Mark Paluch
bf82964474 DATAMONGO-1982 - Updated changelog. 2018-07-26 14:03:20 +02:00
Mark Paluch
2d0495874f DATAMONGO-2029 - Encode collections of UUID and byte array query method arguments to their binary form.
We now convert collections that only contain UUID or byte array items to a BSON list that contains the encoded form of these items. Previously, we only converted single UUID and byte arrays into $binary so lists rendered to e.g. $uuid which does not work for queries.

Encoding is now encapsulated in strategy objects that implement the encoding only for their type. This allows to break up the conditional flow and improve organization of responsibilities.
2018-07-25 15:16:15 +02:00
Mark Paluch
82c91cbb71 DATAMONGO-2030 - Reinstantiate existsBy queries for reactive repositories.
We now support existsBy queries for reactive repositories to align with blocking repository support. ExistsBy support got lost during merging and is now back in place.

Extract boolean flag counting into BooleanUtil.
2018-07-23 16:34:12 +02:00
Christoph Strobl
4d309bd7f0 DATAMONGO-2011 - Relax type check when mapping collections.
Original pull request: #587.
2018-07-13 12:55:07 +02:00
Mark Paluch
6f011b0fa1 DATAMONGO-2021 - Polishing.
Adapt getResources(…) to use the file id and no longer the file name when opening a download stream. Add author tag.

Original pull request: #581.
2018-07-06 13:12:36 +02:00
Niklas Helge Hanft
1a3b9e3c42 DATAMONGO-2021 - Use getObjectId() instead of getFilename() for opening the GridFS download stream.
Using the file name leads to duplicate resource streams as file names are not unique therefore we're using the file's ObjectId to lookup the file content.

Original pull request: #581.
2018-07-06 13:12:36 +02:00
Mark Paluch
5a37468103 DATAMONGO-2016 - Polishing.
Fail gracefully if query string parameter has no value. Reformat test. Convert assertions to AssertJ.

Original pull request: #578.
2018-07-04 11:25:38 +02:00
Stephen Tyler Conrad
d4b0963550 DATAMONGO-2016 - Fix username/password extraction in MongoCredentialPropertyEditor.
MongoCredentialPropertyEditor inspects now the connection URI for the appropriate delimiter tokens. Previously, inspection used the char questionmark for username/password delimiter inspection.

Original pull request: #578.
2018-07-04 11:25:35 +02:00
Mark Paluch
468c497525 DATAMONGO-1969 - After release cleanups. 2018-06-13 21:24:35 +02:00
Mark Paluch
4562f39d7a DATAMONGO-1969 - Prepare next development iteration. 2018-06-13 21:24:33 +02:00
21 changed files with 540 additions and 104 deletions

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.9.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.9.RELEASE</version>
</parent>
<modules>
@@ -27,7 +27,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.0.8.RELEASE</springdata.commons>
<springdata.commons>2.0.9.RELEASE</springdata.commons>
<mongo>3.5.0</mongo>
<mongo.reactivestreams>1.6.0</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.9.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.9.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -50,7 +50,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.9.RELEASE</version>
</dependency>
<!-- reactive -->

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.9.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.9.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -35,6 +35,8 @@ import com.mongodb.MongoCredential;
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Stephen Tyler Conrad
* @author Mark Paluch
* @since 1.7
*/
public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
@@ -164,7 +166,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
private static Properties extractOptions(String text) {
int optionsSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
int dbSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
int dbSeparationIndex = text.lastIndexOf(DATABASE_DELIMITER);
if (optionsSeparationIndex == -1 || dbSeparationIndex > optionsSeparationIndex) {
return new Properties();
@@ -173,7 +175,13 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
Properties properties = new Properties();
for (String option : text.substring(optionsSeparationIndex + 1).split(OPTION_VALUE_DELIMITER)) {
String[] optionArgs = option.split("=");
if (optionArgs.length == 1) {
throw new IllegalArgumentException(String.format("Query parameter '%s' has no value!", optionArgs[0]));
}
properties.put(optionArgs[0], optionArgs[1]);
}

View File

@@ -980,13 +980,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
items.add(read(componentType, (BasicDBObject) element, path));
} else {
if (element instanceof Collection) {
if (!Object.class.equals(rawComponentType) && element instanceof Collection) {
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
throw new MappingException(
String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType, path));
}
}
if (element instanceof List) {
items.add(readCollectionOrArray(componentType, (Collection<Object>) element, path));
} else {

View File

@@ -44,13 +44,14 @@ import com.mongodb.client.gridfs.model.GridFSUploadOptions;
/**
* {@link GridFsOperations} implementation to store content into MongoDB GridFS.
*
*
* @author Oliver Gierke
* @author Philipp Schneider
* @author Thomas Darimont
* @author Martin Baumgartner
* @author Christoph Strobl
* @author Mark Paluch
* @author Niklas Helge Hanft
*/
public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver {
@@ -62,7 +63,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
/**
* Creates a new {@link GridFsTemplate} using the given {@link MongoDbFactory} and {@link MongoConverter}.
*
*
* @param dbFactory must not be {@literal null}.
* @param converter must not be {@literal null}.
*/
@@ -72,7 +73,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
/**
* Creates a new {@link GridFsTemplate} using the given {@link MongoDbFactory} and {@link MongoConverter}.
*
*
* @param dbFactory must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param bucket
@@ -228,7 +229,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
public GridFsResource getResource(String location) {
GridFSFile file = findOne(query(whereFilename().is(location)));
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId())) : null;
}
/*
@@ -246,13 +247,13 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
if (path.isPattern()) {
GridFSFindIterable files = find(query(whereFilename().regex(path.toRegex())));
List<GridFsResource> resources = new ArrayList<GridFsResource>();
List<GridFsResource> resources = new ArrayList<>();
for (GridFSFile file : files) {
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getFilename())));
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId())));
}
return resources.toArray(new GridFsResource[resources.size()]);
return resources.toArray(new GridFsResource[0]);
}
return new GridFsResource[] { getResource(locationPattern) };

View File

@@ -35,7 +35,6 @@ import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecu
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
/**
@@ -150,6 +149,8 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
} else if (isCountQuery()) {
return (q, t, c) -> operation.matching(q).count();
} else if (isExistsQuery()) {
return (q, t, c) -> operation.matching(q).exists();
} else {
return (q, t, c) -> {
@@ -204,6 +205,14 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
*/
protected abstract boolean isCountQuery();
/**
* Returns whether the query should get an exists projection applied.
*
* @return
* @since 2.0.9
*/
protected abstract boolean isExistsQuery();
/**
* Return weather the query should delete matching documents.
*

View File

@@ -0,0 +1,48 @@
/*
* 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
*
* http://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.query;
import lombok.experimental.UtilityClass;
/**
* Utility class containing methods to interact with boolean values.
*
* @author Mark Paluch
* @since 2.0.9
*/
@UtilityClass
class BooleanUtil {
/**
* Count the number of {@literal true} values.
*
* @param values
* @return the number of values that are {@literal true}.
*/
static int countBooleanTrueValues(boolean... values) {
int count = 0;
for (boolean value : values) {
if (value) {
count++;
}
}
return count;
}
}

View File

@@ -16,16 +16,19 @@
package org.springframework.data.mongodb.repository.query;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.UtilityClass;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -206,6 +209,7 @@ class ExpressionEvaluatingParameterBinder {
* @param binding must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
private String getParameterValueForBinding(MongoParameterAccessor accessor, MongoParameters parameters,
ParameterBinding binding) {
@@ -222,43 +226,7 @@ class ExpressionEvaluatingParameterBinder {
return binding.isExpression() ? JSON.serialize(value) : QuotedString.unquote(JSON.serialize(value));
}
if (value instanceof byte[]) {
if (binding.isQuoted()) {
return DatatypeConverter.printBase64Binary((byte[]) value);
}
return encode(new Binary((byte[]) value), BinaryCodec::new);
}
if (value instanceof UUID) {
if (binding.isQuoted()) {
return value.toString();
}
return encode((UUID) value, UuidCodec::new);
}
return JSON.serialize(value);
}
private <T> String encode(T value, Supplier<Codec<T>> defaultCodec) {
Codec<T> codec;
try {
codec = codecRegistry.get((Class<T>) value.getClass());
} catch (CodecConfigurationException exception) {
codec = defaultCodec.get();
}
StringWriter writer = new StringWriter();
codec.encode(new JsonWriter(writer), value, null);
writer.flush();
return writer.toString();
return EncodableValue.create(value).encode(codecRegistry, binding.isQuoted());
}
/**
@@ -480,4 +448,230 @@ class ExpressionEvaluatingParameterBinder {
return quoted.substring(1, quoted.length() - 1);
}
}
/**
* Value object encapsulating a bindable value, that can be encoded to be represented as JSON (BSON).
*
* @author Mark Paluch
*/
abstract static class EncodableValue {
/**
* Obtain a {@link EncodableValue} given {@code value}.
*
* @param value the value to encode, may be {@literal null}.
* @return the {@link EncodableValue} for {@code value}.
*/
@SuppressWarnings("unchecked")
public static EncodableValue create(@Nullable Object value) {
if (value instanceof byte[]) {
return new BinaryValue((byte[]) value);
}
if (value instanceof UUID) {
return new UuidValue((UUID) value);
}
if (value instanceof Collection) {
Collection<?> collection = (Collection<?>) value;
Class<?> commonElement = CollectionUtils.findCommonElementType(collection);
if (commonElement != null) {
if (UUID.class.isAssignableFrom(commonElement)) {
return new UuidCollection((Collection<UUID>) value);
}
if (byte[].class.isAssignableFrom(commonElement)) {
return new BinaryCollectionValue((Collection<byte[]>) value);
}
}
}
return new ObjectValue(value);
}
/**
* Encode the encapsulated value.
*
* @param provider
* @param quoted
* @return
*/
public abstract String encode(CodecRegistry codecRegistry, boolean quoted);
/**
* Encode a {@code value} to JSON.
*
* @param provider
* @param value
* @param defaultCodec
* @param <V>
* @return
*/
protected <V> String encode(CodecRegistry codecRegistry, V value, Supplier<Codec<V>> defaultCodec) {
StringWriter writer = new StringWriter();
doEncode(codecRegistry, writer, value, defaultCodec);
return writer.toString();
}
/**
* Encode a {@link Collection} to JSON and potentially apply a {@link Function mapping function} before encoding.
*
* @param provider
* @param value
* @param mappingFunction
* @param defaultCodec
* @param <I> Input value type.
* @param <V> Target type.
* @return
*/
protected <I, V> String encodeCollection(CodecRegistry codecRegistry, Iterable<I> value,
Function<I, V> mappingFunction, Supplier<Codec<V>> defaultCodec) {
StringWriter writer = new StringWriter();
writer.append("[");
value.forEach(it -> {
if (writer.getBuffer().length() > 1) {
writer.append(", ");
}
doEncode(codecRegistry, writer, mappingFunction.apply(it), defaultCodec);
});
writer.append("]");
writer.flush();
return writer.toString();
}
@SuppressWarnings("unchecked")
private <V> void doEncode(CodecRegistry codecRegistry, StringWriter writer, V value,
Supplier<Codec<V>> defaultCodec) {
Codec<V> codec = getCodec(codecRegistry, (Class<V>) value.getClass(), defaultCodec);
JsonWriter jsonWriter = new JsonWriter(writer);
codec.encode(jsonWriter, value, null);
jsonWriter.flush();
}
private <T> Codec<T> getCodec(CodecRegistry codecRegistry, Class<T> type, Supplier<Codec<T>> defaultCodec) {
try {
return codecRegistry.get(type);
} catch (CodecConfigurationException exception) {
return defaultCodec.get();
}
}
}
/**
* {@link EncodableValue} for {@code byte[]} to render to {@literal $binary}.
*/
@RequiredArgsConstructor
static class BinaryValue extends EncodableValue {
private final byte[] value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
if (quoted) {
return DatatypeConverter.printBase64Binary(this.value);
}
return encode(codecRegistry, new Binary(this.value), BinaryCodec::new);
}
}
/**
* {@link EncodableValue} for {@link Collection} containing only {@code byte[]} items to render to a BSON list
* containing {@literal $binary}.
*/
@RequiredArgsConstructor
static class BinaryCollectionValue extends EncodableValue {
private final Collection<byte[]> value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
return encodeCollection(codecRegistry, this.value, Binary::new, BinaryCodec::new);
}
}
/**
* {@link EncodableValue} for {@link UUID} to render to {@literal $binary}.
*/
@RequiredArgsConstructor
static class UuidValue extends EncodableValue {
private final UUID value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
if (quoted) {
return this.value.toString();
}
return encode(codecRegistry, this.value, UuidCodec::new);
}
}
/**
* {@link EncodableValue} for {@link Collection} containing only {@link UUID} items to render to a BSON list
* containing {@literal $binary}.
*/
@RequiredArgsConstructor
static class UuidCollection extends EncodableValue {
private final Collection<UUID> value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
return encodeCollection(codecRegistry, this.value, Function.identity(), UuidCodec::new);
}
}
/**
* Fallback-{@link EncodableValue} for {@link Object}-typed values.
*/
@RequiredArgsConstructor
static class ObjectValue extends EncodableValue {
private final @Nullable Object value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
return JSON.serialize(this.value);
}
}
}

View File

@@ -134,6 +134,15 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
return tree.isCountProjection();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return tree.isExistsProjection();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()

View File

@@ -40,13 +40,14 @@ import org.springframework.util.Assert;
*/
public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!";
private static final Logger LOG = LoggerFactory.getLogger(ReactiveStringBasedMongoQuery.class);
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
private final String query;
private final String fieldSpec;
private final boolean isCountQuery;
private final boolean isExistsQuery;
private final boolean isDeleteQuery;
private final List<ParameterBinding> queryParameterBindings;
private final List<ParameterBinding> fieldSpecParameterBindings;
@@ -92,11 +93,23 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
method.getFieldSpecification(), this.fieldSpecParameterBindings);
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
if (method.hasAnnotatedQuery()) {
if (isCountQuery && isDeleteQuery) {
throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method));
org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation();
this.isCountQuery = queryAnnotation.count();
this.isExistsQuery = queryAnnotation.exists();
this.isDeleteQuery = queryAnnotation.delete();
if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) {
throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method));
}
} else {
this.isCountQuery = false;
this.isExistsQuery = false;
this.isDeleteQuery = false;
}
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
@@ -132,6 +145,15 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
return isCountQuery;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return isExistsQuery;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
@@ -150,4 +172,9 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
return false;
}
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
boolean isDeleteQuery) {
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
}

View File

@@ -169,11 +169,6 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
return this.isDeleteQuery;
}
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
boolean isDeleteQuery) {
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
@@ -183,18 +178,9 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
return false;
}
private static int countBooleanValues(boolean... values) {
int count = 0;
for (boolean value : values) {
if (value) {
count++;
}
}
return count;
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
boolean isDeleteQuery) {
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
/**

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.data.mongodb.config;
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@@ -34,6 +32,7 @@ import com.mongodb.MongoCredential;
* Unit tests for {@link MongoCredentialPropertyEditor}.
*
* @author Christoph Strobl
* @author Stephen Tyler Conrad
*/
public class MongoCredentialPropertyEditorUnitTests {
@@ -54,6 +53,10 @@ public class MongoCredentialPropertyEditorUnitTests {
static final String USER_4_ENCODED_PWD;
static final String USER_4_DB = "targaryen";
static final String USER_5_NAME = "lyanna";
static final String USER_5_PWD = "random?password";
static final String USER_5_DB = "mormont";
static final String USER_1_AUTH_STRING = USER_1_NAME + ":" + USER_1_PWD + "@" + USER_1_DB;
static final String USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM = USER_1_AUTH_STRING + "?uri.authMechanism=PLAIN";
@@ -66,6 +69,10 @@ public class MongoCredentialPropertyEditorUnitTests {
static final String USER_4_AUTH_STRING;
static final String USER_5_AUTH_STRING = USER_5_NAME + ":" + USER_5_PWD + "@" + USER_5_DB;
static final String USER_5_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM = USER_5_AUTH_STRING + "?uri.authMechanism=PLAIN";
static final String USER_5_AUTH_STRING_WITH_QUERY_ARGS = USER_5_AUTH_STRING + "?uri.authMechanism=PLAIN&foo=&bar";
static final MongoCredential USER_1_CREDENTIALS = MongoCredential.createCredential(USER_1_NAME, USER_1_DB,
USER_1_PWD.toCharArray());
static final MongoCredential USER_1_CREDENTIALS_PLAIN_AUTH = MongoCredential.createPlainCredential(USER_1_NAME,
@@ -81,6 +88,11 @@ public class MongoCredentialPropertyEditorUnitTests {
static final MongoCredential USER_4_CREDENTIALS = MongoCredential.createCredential(USER_4_PLAIN_NAME, USER_4_DB,
USER_4_PLAIN_PWD.toCharArray());
static final MongoCredential USER_5_CREDENTIALS = MongoCredential.createCredential(USER_5_NAME, USER_5_DB,
USER_5_PWD.toCharArray());
static final MongoCredential USER_5_CREDENTIALS_PLAIN_AUTH = MongoCredential.createPlainCredential(USER_5_NAME,
USER_5_DB, USER_5_PWD.toCharArray());
MongoCredentialPropertyEditor editor;
static {
@@ -108,7 +120,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(null);
assertThat(editor.getValue(), nullValue());
assertThat(getValue()).isNull();
}
@Test // DATAMONGO-1158
@@ -116,7 +128,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(" ");
assertThat(editor.getValue(), nullValue());
assertThat(getValue()).isNull();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1158
@@ -135,7 +147,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(USER_1_AUTH_STRING);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS);
}
@Test // DATAMONGO-1158
@@ -144,7 +156,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH));
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH);
}
@Test // DATAMONGO-1158
@@ -154,38 +166,37 @@ public class MongoCredentialPropertyEditorUnitTests {
editor
.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(USER_1_AUTH_STRING, USER_2_AUTH_STRING)));
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS);
}
@Test // DATAMONGO-1158
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndAuthOptions() {
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(
USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING_WITH_MONGODB_CR_AUTH_MECHANISM)));
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays
.asList(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING_WITH_MONGODB_CR_AUTH_MECHANISM)));
assertThat((List<MongoCredential>) editor.getValue(),
contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH));
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH);
}
@Test // DATAMONGO-1158
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndMixedOptions() {
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(
USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING)));
editor.setAsText(StringUtils.collectionToCommaDelimitedString(
Arrays.asList(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING)));
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS);
}
@Test // DATAMONGO-1257
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() {
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList("'" + USER_1_AUTH_STRING + "'", "'"
+ USER_2_AUTH_STRING + "'")));
editor.setAsText(StringUtils.collectionToCommaDelimitedString(
Arrays.asList("'" + USER_1_AUTH_STRING + "'", "'" + USER_2_AUTH_STRING + "'")));
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS);
}
@Test // DATAMONGO-1257
@@ -194,7 +205,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("'" + USER_1_AUTH_STRING + "'");
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS);
}
@Test // DATAMONGO-1257
@@ -203,7 +214,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(USER_3_AUTH_STRING_WITH_X509_AUTH_MECHANISM);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_3_CREDENTIALS_X509_AUTH));
assertThat(getValue()).contains(USER_3_CREDENTIALS_X509_AUTH);
}
@Test // DATAMONGO-1257
@@ -212,7 +223,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("tyrion?uri.authMechanism=MONGODB-X509");
assertThat((List<MongoCredential>) editor.getValue(), contains(MongoCredential.createMongoX509Credential("tyrion")));
assertThat(getValue()). contains(MongoCredential.createMongoX509Credential("tyrion"));
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1257
@@ -220,7 +231,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("tyrion?uri.authMechanism=MONGODB-CR");
editor.getValue();
getValue();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1257
@@ -228,15 +239,44 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("tyrion@?uri.authMechanism=MONGODB-CR");
editor.getValue();
getValue();
}
@Test // DATAMONGO-1317
@SuppressWarnings("unchecked")
public void encodedUserNameAndPasswrodShouldBeDecoded() throws UnsupportedEncodingException {
public void encodedUserNameAndPasswordShouldBeDecoded() {
editor.setAsText(USER_4_AUTH_STRING);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_4_CREDENTIALS));
assertThat(getValue()).contains(USER_4_CREDENTIALS);
}
@Test // DATAMONGO-2016
@SuppressWarnings("unchecked")
public void passwordWithQuestionMarkShouldNotBeInterpretedAsOptionString() {
editor.setAsText(USER_5_AUTH_STRING);
assertThat(getValue()).contains(USER_5_CREDENTIALS);
}
@Test // DATAMONGO-2016
@SuppressWarnings("unchecked")
public void passwordWithQuestionMarkShouldNotBreakParsingOfOptionString() {
editor.setAsText(USER_5_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM);
assertThat(getValue()).contains(USER_5_CREDENTIALS_PLAIN_AUTH);
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-2016
@SuppressWarnings("unchecked")
public void failsGracefullyOnEmptyQueryArgument() {
editor.setAsText(USER_5_AUTH_STRING_WITH_QUERY_ARGS);
}
@SuppressWarnings("unchecked")
private List<MongoCredential> getValue() {
return (List<MongoCredential>) editor.getValue();
}
}

View File

@@ -1886,6 +1886,15 @@ public class MappingMongoConverterUnitTests {
assertThat(result.nestedFloats).isEqualTo(new float[][][] { { { 1.0f, 2.0f } } });
}
@Test // DATAMONGO-2011
public void readsNestedListsToObjectCorrectly() {
List<String> values = Arrays.asList("ONE", "TWO");
org.bson.Document source = new org.bson.Document("value", Collections.singletonList(values));
assertThat(converter.read(Attribute.class, source).value).isInstanceOf(List.class);
}
static class GenericType<T> {
T content;
}

View File

@@ -313,6 +313,11 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
StepVerifier.create(repository.findFirstByLastname(dave.getLastname())).expectNextCount(1).verifyComplete();
}
@Test // DATAMONGO-2030
public void shouldReturnExistsBy() {
StepVerifier.create(repository.existsByLastname(dave.getLastname())).expectNext(true).verifyComplete();
}
interface ReactivePersonRepository extends ReactiveMongoRepository<Person, String> {
Flux<Person> findByLastname(String lastname);
@@ -340,6 +345,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
Flux<Person> findPersonByLocationNear(Point point, Distance maxDistance);
Mono<Boolean> existsByLastname(String lastname);
Mono<Person> findFirstByLastname(String lastname);
}

View File

@@ -122,6 +122,14 @@ public class ReactiveStringBasedMongoQueryUnitTests {
createQueryForMethod("invalidMethod", String.class);
}
@Test // DATAMONGO-2030
public void shouldSupportExistsProjection() throws Exception {
ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class);
assertThat(mongoQuery.isExistsQuery(), is(true));
}
@Test // DATAMONGO-1444
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
@@ -260,5 +268,8 @@ public class ReactiveStringBasedMongoQueryUnitTests {
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}")
Flux<Person> findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2);
@Query(value = "{ 'lastname' : ?0 }", exists = true)
Mono<Boolean> existsByLastname(String lastname);
}
}

View File

@@ -23,6 +23,7 @@ import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -322,6 +323,21 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-2029
public void shouldSupportNonQuotedBinaryCollectionDataReplacement() {
byte[] binaryData = "Matthews".getBytes(StandardCharsets.UTF_8);
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
(Object) Arrays.asList(binaryData));
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinaryIn", List.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { $in: [{'$binary' : '"
+ DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : '" + BSON.B_GENERAL + "'}] }}");
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-1911
public void shouldSupportNonQuotedUUIDReplacement() {
@@ -336,6 +352,23 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-2029
public void shouldSupportNonQuotedUUIDCollectionReplacement() {
UUID uuid1 = UUID.fromString("864de43b-e3ea-f1e4-3663-fb8240b659b9");
UUID uuid2 = UUID.fromString("864de43b-cafe-f1e4-3663-fb8240b659b9");
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
(Object) Arrays.asList(uuid1, uuid2));
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsUUIDIn", List.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(
"{'lastname' : { $in: [{ $binary : \"5PHq4zvkTYa5WbZAgvtjNg==\", $type : \"03\" }, { $binary : \"5PH+yjvkTYa5WbZAgvtjNg==\", $type : \"03\" }]}}");
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-1911
public void shouldSupportQuotedUUIDReplacement() {
@@ -580,9 +613,15 @@ public class StringBasedMongoQueryUnitTests {
@Query("{ 'lastname' : ?0 }")
Person findByLastnameAsBinary(byte[] lastname);
@Query("{ 'lastname' : { $in: ?0} }")
Person findByLastnameAsBinaryIn(List<byte[]> lastname);
@Query("{ 'lastname' : ?0 }")
Person findByLastnameAsUUID(UUID lastname);
@Query("{ 'lastname' : { $in : ?0} }")
Person findByLastnameAsUUIDIn(List<UUID> lastname);
@Query("{ 'lastname' : '?0' }")
Person findByLastnameAsStringUUID(UUID lastname);

View File

@@ -1,6 +1,55 @@
Spring Data MongoDB Changelog
=============================
Changes in version 2.0.9.RELEASE (2018-07-26)
---------------------------------------------
* DATAMONGO-2030 - Reactive repositories don't handle existsByProperty properly.
* DATAMONGO-2029 - String query methods do not convert List<UUID> and List<byte[]> to their correct representation.
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
* DATAMONGO-2011 - Getting exception in MappingMongoConverter while trying to map array of arrays into Object.
* DATAMONGO-2007 - Release 2.0.9 (Kay SR9).
Changes in version 2.1.0.RC1 (2018-07-26)
-----------------------------------------
* DATAMONGO-2030 - Reactive repositories don't handle existsByProperty properly.
* DATAMONGO-2029 - String query methods do not convert List<UUID> and List<byte[]> to their correct representation.
* DATAMONGO-2028 - EntityOperations does not apply conversion to Mongo types.
* DATAMONGO-2026 - Final id field causes UnsupportedOperationException when reading object.
* DATAMONGO-2021 - Use correct document identifier on GridFsOperations getResource method.
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
* DATAMONGO-2012 - Upgrade to MongoDB java driver 3.8 and reactive streams 1.9.
* DATAMONGO-2011 - Getting exception in MappingMongoConverter while trying to map array of arrays into Object.
* DATAMONGO-2010 - SpringDataMongodbSerializer does not convert 'in' predicate for nested String id properties mapping to ObjectId.
* DATAMONGO-2005 - Switch reactive transactions API over to Flux.usingWhen.
* DATAMONGO-2004 - Lazily resolve DBRefs during constructor creation of the enclosing entity.
* DATAMONGO-2003 - MongoQueryCreator will not create query correctly when passing Pattern Object with options.
* DATAMONGO-2002 - Criteria instances using different regex Patterns are considered equal.
* DATAMONGO-2001 - Count within session should return only the total count of documents visible to the specific session.
* DATAMONGO-1998 - SpringDataMongodbSerializer cannot convert query on nested id.
* DATAMONGO-1996 - ChangeStreamEvent Aggregator filter doesn't work for nested fields.
* DATAMONGO-1993 - Change default fullDocumentLookup in ReactiveMongoTemplate.changeStream.
* DATAMONGO-1992 - Add support for immutable objects.
* DATAMONGO-1990 - Adapt build to changes in is-new-handling.
* DATAMONGO-1988 - String ID not working for embeded objects.
* DATAMONGO-1987 - Upgarde test infrastructure to MongoDB 4.0-rc0.
* DATAMONGO-1986 - Aggregation MatchOperation fails to convert query.
* DATAMONGO-1983 - Reference documentation does not include Transaction and sessions.
* DATAMONGO-1982 - Release 2.1 RC1 (Lovelace).
* DATAMONGO-1979 - Adding Support For Sorting in @Query Annotation and Creating an alias for value.
* DATAMONGO-1919 - Decouple reactive mongo bits from blocking MongoClient.
* DATAMONGO-1848 - Migrate to Document API-based Querydsl implementation.
* DATAMONGO-1842 - Optimistic locking allows ReactiveMongoTemplate to modify immutable objects.
* DATAMONGO-1827 - Allow document replacements via MongoCollection.findOneAndReplace(…).
* DATAMONGO-1810 - Querydsl predicate using IN operator fails for DBRef.
* DATAMONGO-1434 - Convert Exceptions into Spring Exception hierarchy when going through Querydsl.
* DATAMONGO-1311 - Add an option to specify the cursor.batchSize() for repository methods returning streams.
* DATAMONGO-1185 - Provide lifecycle events when using QueryDSL.
* DATAMONGO-700 - Events not triggered during Querydsl repository usage.
* DATAMONGO-595 - Expose new QueryDSL elemMatch feature.
* DATAMONGO-362 - QueryDSL does not work with DBRef.
Changes in version 2.0.8.RELEASE (2018-06-13)
---------------------------------------------
* DATAMONGO-2003 - MongoQueryCreator will not create query correctly when passing Pattern Object with options.

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 2.0.8
Spring Data MongoDB 2.0.9
Copyright (c) [2010-2015] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").