Compare commits

..

30 Commits

Author SHA1 Message Date
Mark Paluch
61bbc9ab7f DATAMONGO-2500 - Release version 2.2.7 (Moore SR7). 2020-04-28 14:35:55 +02:00
Mark Paluch
9e802a59c7 DATAMONGO-2500 - Prepare 2.2.7 (Moore SR7). 2020-04-28 14:35:24 +02:00
Mark Paluch
38e1e632a7 DATAMONGO-2500 - Updated changelog. 2020-04-28 14:35:16 +02:00
Mark Paluch
89cf78cc4a DATAMONGO-2484 - Updated changelog. 2020-04-28 11:59:03 +02:00
Mark Paluch
cecd47d679 DATAMONGO-2529 - Ensure that MappingMongoConverter.read(…) is never called with null.
Previously, various methods attempted to pass a null argument as source for the converter. The API is non-null and implementations relying on these constraints were easily breakable.

We now make sure that the source is never null.
2020-04-23 15:25:45 +02:00
Mark Paluch
ed35e577af DATAMONGO-2504 - Polishing.
Update equals/hashCode implementation to use the Spring Data form. Make fields final where possible. Use diamond syntax. Reorder methods. Reformat code. Extend tests.

Original pull request: #848.
2020-04-23 15:25:45 +02:00
ddebray
f54cf40eda DATAMONGO-2504 - Add hashCode and equals to TextCriteria and Term.
Original pull request: #848.
2020-04-23 12:29:07 +02:00
Christoph Strobl
5314e6f8bb DATAMONGO-2513 - Fix Eq aggregation operator comparing collection values.
Original pull request: #855.
2020-04-22 11:47:02 +02:00
Mark Paluch
b7b2709177 DATAMONGO-2523 - Polishing.
Reformat code.

Original pull request: #859.
2020-04-22 10:08:33 +02:00
Christoph Strobl
34c47e84c0 DATAMONGO-2523 - Fix Json binding of SpEL expressions in arrays.
The closing bracket must not have a leading whitespace.

Original pull request: #859.
2020-04-22 10:08:30 +02:00
Mark Paluch
f7d91184a0 DATAMONGO-2517 - Polishing.
Reformat code.

Original pull request: #857.
2020-04-21 16:02:36 +02:00
Christoph Strobl
eeddc860f7 DATAMONGO-2517 - Fix invalid entity creation for text queries.
Fix a glitch in the MappingMongoConverter that uses the single String argument constructor (since it matches in type and parameter count to the given input string) to falsely instantiate an Entity when it should not.

Original pull request: #857.
2020-04-21 16:02:24 +02:00
Christoph Strobl
bcefdd209b DATAMONGO-2506 - Provide meaningful error message when using unsupported return type in repository aggregation method.
We improved the error message for unsupported return types instead of running into an IllegalArgumentException for unique results.

Original pull request: #851.
2020-04-07 14:59:30 +02:00
Mark Paluch
3f1fea2d19 DATAMONGO-2502 - Polishing.
Extend tests. Fix generics. Consistently use compiled patterns for positional placeholder removal.

Original pull request: #847.
2020-04-07 14:47:42 +02:00
Christoph Strobl
665322a69a DATAMONGO-2502 - Fix nested array path mapping for updates.
Original pull request: #847.
2020-04-07 14:17:05 +02:00
Mark Paluch
3e59bc3b38 DATAMONGO-2492 - Updated changelog. 2020-03-31 15:08:52 +02:00
Mark Paluch
1752931dde DATAMONGO-2485 - After release cleanups. 2020-03-25 10:58:27 +01:00
Mark Paluch
4b9bae1656 DATAMONGO-2485 - Prepare next development iteration. 2020-03-25 10:58:26 +01:00
Mark Paluch
74c08fa8aa DATAMONGO-2485 - Release version 2.2.6 (Moore SR6). 2020-03-25 10:46:02 +01:00
Mark Paluch
628aad8f64 DATAMONGO-2485 - Prepare 2.2.6 (Moore SR6). 2020-03-25 10:45:35 +01:00
Mark Paluch
39c8672e6d DATAMONGO-2485 - Updated changelog. 2020-03-25 10:45:28 +01:00
Christoph Strobl
620991ddee DATAMONGO-2300 - Polishing.
Move null check to event publishing logic.

Original Pull Request: #763
2020-03-23 10:11:41 +01:00
Heesu Jung
ba8f28f623 DATAMONGO-2300 - Add check rawType is null in readMap.
Original Pull Request: #763
2020-03-23 10:01:50 +01:00
Mark Paluch
6389055d3a DATAMONGO-2497 - Update documentation regarding @Transient properties usage in the persistence constructor. 2020-03-19 15:37:37 +01:00
Christoph Strobl
4465ed9819 DATAMONGO-2445 - Deprecate ReactiveGridFsOperations using AsyncInputStream.
Methods using AsyncInputStream will be removed in 3.0. Please use the ones accepting a Publisher.

Original pull request: #843.
2020-03-19 09:42:49 +01:00
Mark Paluch
8dc97e5d01 DATAMONGO-2488 - Polishing.
Simplify conditional entity check.

Original pull request: #841.
2020-03-11 14:38:17 +01:00
Christoph Strobl
a037c50961 DATAMONGO-2488 - Fix nested array path field name mapping.
Original pull request: #841.
2020-03-11 14:38:16 +01:00
Jens Schauder
28d5f02e15 DATAMONGO-2473 - Updated changelog. 2020-03-11 09:59:36 +01:00
Mark Paluch
e65a353fc4 DATAMONGO-2453 - After release cleanups. 2020-02-26 11:54:09 +01:00
Mark Paluch
42400e7836 DATAMONGO-2453 - Prepare next development iteration. 2020-02-26 11:54:08 +01:00
25 changed files with 498 additions and 59 deletions

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.5.RELEASE</version>
<version>2.2.7.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.2.5.RELEASE</version>
<version>2.2.7.RELEASE</version>
</parent>
<modules>
@@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.2.5.RELEASE</springdata.commons>
<springdata.commons>2.2.7.RELEASE</springdata.commons>
<mongo>3.11.2</mongo>
<mongo.reactivestreams>1.12.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.2.5.RELEASE</version>
<version>2.2.7.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.5.RELEASE</version>
<version>2.2.7.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.2.5.RELEASE</version>
<version>2.2.7.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -3155,12 +3155,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Nullable
public T doWith(@Nullable Document object) {
T source = null;
if (null != object) {
maybeEmitEvent(new AfterLoadEvent<>(object, type, collectionName));
source = reader.read(type, object);
}
T source = reader.read(type, object);
if (null != source) {
maybeEmitEvent(new AfterConvertEvent<>(object, source, collectionName));
}
@@ -3200,9 +3201,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
: targetType;
if (null != object) {
maybeEmitEvent(new AfterLoadEvent<>(object, targetType, collectionName));
}
maybeEmitEvent(new AfterLoadEvent<>(object, targetType, collectionName));
Object source = reader.read(typeToRead, object);
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -100,14 +101,14 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
return value;
}
protected List<Object> append(Object value) {
protected List<Object> append(Object value, Expand expandList) {
if (this.value instanceof List) {
List<Object> clone = new ArrayList<Object>((List) this.value);
if (value instanceof List) {
clone.addAll((List) value);
if (value instanceof Collection && Expand.EXPAND_VALUES.equals(expandList)) {
clone.addAll((Collection<?>) value);
} else {
clone.add(value);
}
@@ -117,6 +118,17 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
return Arrays.asList(this.value, value);
}
/**
* Expand a nested list of values to single entries or keep the list.
*/
protected enum Expand {
EXPAND_VALUES, KEEP_SOURCE
}
protected List<Object> append(Object value) {
return append(value, Expand.EXPAND_VALUES);
}
@SuppressWarnings("unchecked")
protected java.util.Map<String, Object> append(String key, Object value) {

View File

@@ -411,7 +411,7 @@ public class ComparisonOperators {
public Cmp compareToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Cmp(append(value));
return new Cmp(append(value, Expand.KEEP_SOURCE));
}
}
@@ -488,7 +488,7 @@ public class ComparisonOperators {
public Eq equalToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Eq(append(value));
return new Eq(append(value, Expand.KEEP_SOURCE));
}
}
@@ -873,7 +873,7 @@ public class ComparisonOperators {
public Ne notEqualToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Ne(append(value));
return new Ne(append(value, Expand.KEEP_SOURCE));
}
}
}

View File

@@ -90,6 +90,7 @@ import com.mongodb.DBRef;
* @author Christoph Strobl
* @author Jordi Llach
* @author Mark Paluch
* @author Heesu Jung
*/
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
@@ -227,11 +228,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@Nullable
@SuppressWarnings("unchecked")
private <S extends Object> S read(TypeInformation<S> type, @Nullable Bson bson, ObjectPath path) {
private <S extends Object> S read(TypeInformation<S> type, Bson bson, ObjectPath path) {
if (null == bson) {
return null;
}
Assert.notNull(bson, "Bson must not be null!");
TypeInformation<? extends S> typeToUse = typeMapper.readType(bson, type);
Class<? extends S> rawType = typeToUse.getType();
@@ -1262,9 +1261,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (conversions.isSimpleType(obj.getClass())) {
// Doesn't need conversion
return getPotentiallyConvertedSimpleWrite(obj,
typeInformation != null ? typeInformation.getType() : Object.class);
Class<?> conversionTargetType;
if (typeInformation != null && conversions.isSimpleType(typeInformation.getType())) {
conversionTargetType = typeInformation.getType();
} else {
conversionTargetType = Object.class;
}
return getPotentiallyConvertedSimpleWrite(obj, conversionTargetType);
}
if (obj instanceof List) {
@@ -1574,7 +1580,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@Nullable
private <T> T readAndConvertDBRef(@Nullable DBRef dbref, TypeInformation<?> type, ObjectPath path,
final Class<?> rawType) {
@Nullable Class<?> rawType) {
List<T> result = bulkReadAndConvertDBRefs(Collections.singletonList(dbref), type, path, rawType);
return CollectionUtils.isEmpty(result) ? null : result.iterator().next();
@@ -1597,7 +1603,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@SuppressWarnings("unchecked")
private <T> List<T> bulkReadAndConvertDBRefs(List<DBRef> dbrefs, TypeInformation<?> type, ObjectPath path,
final Class<?> rawType) {
@Nullable Class<?> rawType) {
if (CollectionUtils.isEmpty(dbrefs)) {
return Collections.emptyList();
@@ -1612,16 +1618,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
for (Document document : referencedRawDocuments) {
T target = null;
if (document != null) {
maybeEmitEvent(new AfterLoadEvent<>(document, (Class<T>) rawType, collectionName));
}
final T target = (T) read(type, document, path);
targeList.add(target);
maybeEmitEvent(
new AfterLoadEvent<>(document, (Class<T>) (rawType != null ? rawType : Object.class), collectionName));
target = (T) read(type, document, path);
}
if (target != null) {
maybeEmitEvent(new AfterConvertEvent<>(document, target, collectionName));
}
targeList.add(target);
}
return targeList;

View File

@@ -24,7 +24,6 @@ import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Example;
@@ -175,7 +174,7 @@ public class QueryMapper {
}
Document mappedSort = new Document();
for(Map.Entry<String,Object> entry : BsonUtils.asMap(sortObject).entrySet()) {
for (Map.Entry<String, Object> entry : BsonUtils.asMap(sortObject).entrySet()) {
Field field = createPropertyField(entity, entry.getKey(), mappingContext);
mappedSort.put(field.getMappedKey(), entry.getValue());
@@ -420,7 +419,7 @@ public class QueryMapper {
return false;
}
Class<? extends Object> type = value.getClass();
Class<?> type = value.getClass();
MongoPersistentProperty property = documentField.getProperty();
if (property.getActualType().isAssignableFrom(type)) {
@@ -444,7 +443,7 @@ public class QueryMapper {
protected Object convertSimpleOrDocument(Object source, @Nullable MongoPersistentEntity<?> entity) {
if (source instanceof Example) {
return exampleMapper.getMappedExample((Example) source, entity);
return exampleMapper.getMappedExample((Example<?>) source, entity);
}
if (source instanceof List) {
@@ -923,6 +922,8 @@ public class QueryMapper {
*/
protected static class MetadataBackedField extends Field {
private static final Pattern POSITIONAL_PARAMETER_PATTERN = Pattern.compile("\\.\\$(\\[.*?\\])?|\\.\\d+");
private static final Pattern DOT_POSITIONAL_PATTERN = Pattern.compile("\\.\\d+");
private static final String INVALID_ASSOCIATION_REFERENCE = "Invalid path reference %s! Associations can only be pointed to directly or via their id property!";
private final MongoPersistentEntity<?> entity;
@@ -964,7 +965,7 @@ public class QueryMapper {
this.entity = entity;
this.mappingContext = context;
this.path = getPath(name);
this.path = getPath(removePlaceholders(POSITIONAL_PARAMETER_PATTERN, name));
this.property = path == null ? property : path.getLeafProperty();
this.association = findAssociation();
}
@@ -1072,7 +1073,7 @@ public class QueryMapper {
}
/**
* Returns the {@link PersistentPropertyPath} for the given <code>pathExpression</code>.
* Returns the {@link PersistentPropertyPath} for the given {@code pathExpression}.
*
* @param pathExpression
* @return
@@ -1080,8 +1081,8 @@ public class QueryMapper {
@Nullable
private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpression) {
String rawPath = pathExpression.replaceAll("\\.\\d+", "") //
.replaceAll(POSITIONAL_OPERATOR.pattern(), "");
String rawPath = removePlaceholders(POSITIONAL_OPERATOR,
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
PropertyPath path = forName(rawPath);
if (path == null || isPathToJavaLangClassProperty(path)) {
@@ -1158,7 +1159,7 @@ public class QueryMapper {
* @return
*/
protected Converter<MongoPersistentProperty, String> getPropertyConverter() {
return new PositionParameterRetainingPropertyKeyConverter(name);
return new PositionParameterRetainingPropertyKeyConverter(name, mappingContext);
}
/**
@@ -1169,7 +1170,15 @@ public class QueryMapper {
* @since 1.7
*/
protected Converter<MongoPersistentProperty, String> getAssociationConverter() {
return new AssociationConverter(getAssociation());
return new AssociationConverter(name, getAssociation());
}
protected MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
return mappingContext;
}
private static String removePlaceholders(Pattern pattern, String raw) {
return pattern.matcher(raw).replaceAll("");
}
/**
@@ -1180,8 +1189,9 @@ public class QueryMapper {
private final KeyMapper keyMapper;
public PositionParameterRetainingPropertyKeyConverter(String rawKey) {
this.keyMapper = new KeyMapper(rawKey);
public PositionParameterRetainingPropertyKeyConverter(String rawKey,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> ctx) {
this.keyMapper = new KeyMapper(rawKey, ctx);
}
/*
@@ -1223,7 +1233,8 @@ public class QueryMapper {
private final Iterator<String> iterator;
public KeyMapper(String key) {
public KeyMapper(String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
this.iterator = Arrays.asList(key.split("\\.")).iterator();
this.iterator.next();
@@ -1243,6 +1254,7 @@ public class QueryMapper {
while (inspect) {
String partial = iterator.next();
boolean isPositional = (isPositionalParameter(partial) && (property.isMap() || property.isCollectionLike()));
if (isPositional) {
@@ -1255,7 +1267,7 @@ public class QueryMapper {
return mappedName.toString();
}
private static boolean isPositionalParameter(String partial) {
static boolean isPositionalParameter(String partial) {
if ("$".equals(partial)) {
return true;
@@ -1283,6 +1295,7 @@ public class QueryMapper {
*/
protected static class AssociationConverter implements Converter<MongoPersistentProperty, String> {
private final String name;
private final MongoPersistentProperty property;
private boolean associationFound;
@@ -1291,10 +1304,11 @@ public class QueryMapper {
*
* @param association must not be {@literal null}.
*/
public AssociationConverter(Association<MongoPersistentProperty> association) {
public AssociationConverter(String name, Association<MongoPersistentProperty> association) {
Assert.notNull(association, "Association must not be null!");
this.property = association.getInverse();
this.name = name;
}
/*
@@ -1312,6 +1326,12 @@ public class QueryMapper {
associationFound = true;
}
if (associationFound) {
if (name.endsWith("$") && property.isCollectionLike()) {
return source.getFieldName() + ".$";
}
}
return source.getFieldName();
}
}

View File

@@ -272,6 +272,7 @@ public class UpdateMapper extends QueryMapper {
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
*/
private static class MetadataBackedUpdateField extends MetadataBackedField {
@@ -289,7 +290,7 @@ public class UpdateMapper extends QueryMapper {
public MetadataBackedUpdateField(MongoPersistentEntity<?> entity, String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(key.replaceAll("\\.\\$(\\[.*\\])?", ""), entity, mappingContext);
super(key, entity, mappingContext);
this.key = key;
}
@@ -308,7 +309,7 @@ public class UpdateMapper extends QueryMapper {
*/
@Override
protected Converter<MongoPersistentProperty, String> getPropertyConverter() {
return new PositionParameterRetainingPropertyKeyConverter(key);
return new PositionParameterRetainingPropertyKeyConverter(key, getMappingContext());
}
/*
@@ -317,7 +318,7 @@ public class UpdateMapper extends QueryMapper {
*/
@Override
protected Converter<MongoPersistentProperty, String> getAssociationConverter() {
return new UpdateAssociationConverter(getAssociation(), key);
return new UpdateAssociationConverter(getMappingContext(), getAssociation(), key);
}
/**
@@ -334,10 +335,12 @@ public class UpdateMapper extends QueryMapper {
*
* @param association must not be {@literal null}.
*/
public UpdateAssociationConverter(Association<MongoPersistentProperty> association, String key) {
public UpdateAssociationConverter(
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
Association<MongoPersistentProperty> association, String key) {
super(association);
this.mapper = new KeyMapper(key);
super(key, association);
this.mapper = new KeyMapper(key, mappingContext);
}
/*

View File

@@ -15,7 +15,10 @@
*/
package org.springframework.data.mongodb.core.query;
import static org.springframework.util.ObjectUtils.*;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* A {@link Term} defines one or multiple words {@link Type#WORD} or phrases {@link Type#PHRASE} to be used in the
@@ -90,6 +93,47 @@ public class Term {
return negated ? negateRaw(formatted) : formatted;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Term)) {
return false;
}
Term term = (Term) o;
return ObjectUtils.nullSafeEquals(negated, term.negated) && ObjectUtils.nullSafeEquals(type, term.type)
&& ObjectUtils.nullSafeEquals(raw, term.raw);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += ObjectUtils.nullSafeHashCode(type);
result += ObjectUtils.nullSafeHashCode(raw);
result += ObjectUtils.nullSafeHashCode(negated);
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getFormatted();

View File

@@ -21,6 +21,7 @@ import java.util.List;
import org.bson.Document;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -34,7 +35,7 @@ import org.springframework.util.StringUtils;
public class TextCriteria implements CriteriaDefinition {
private final List<Term> terms;
private @Nullable String language;
private final @Nullable String language;
private @Nullable Boolean caseSensitive;
private @Nullable Boolean diacriticSensitive;
@@ -51,7 +52,7 @@ public class TextCriteria implements CriteriaDefinition {
private TextCriteria(@Nullable String language) {
this.language = language;
this.terms = new ArrayList<Term>();
this.terms = new ArrayList<>();
}
/**
@@ -231,9 +232,47 @@ public class TextCriteria implements CriteriaDefinition {
return new Document("$text", document);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TextCriteria)) {
return false;
}
TextCriteria that = (TextCriteria) o;
return ObjectUtils.nullSafeEquals(terms, that.terms) && ObjectUtils.nullSafeEquals(language, that.language)
&& ObjectUtils.nullSafeEquals(caseSensitive, that.caseSensitive)
&& ObjectUtils.nullSafeEquals(diacriticSensitive, that.diacriticSensitive);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += ObjectUtils.nullSafeHashCode(terms);
result += ObjectUtils.nullSafeHashCode(language);
result += ObjectUtils.nullSafeHashCode(caseSensitive);
result += ObjectUtils.nullSafeHashCode(diacriticSensitive);
return result;
}
private String join(Iterable<Term> terms) {
List<String> result = new ArrayList<String>();
List<String> result = new ArrayList<>();
for (Term term : terms) {
if (term != null) {

View File

@@ -111,7 +111,10 @@ public interface ReactiveGridFsOperations {
* @param metadata can be {@literal null}
* @return a {@link Mono} emitting the {@link ObjectId} of the {@link com.mongodb.client.gridfs.model.GridFSFile} just
* created.
* @deprecated since 2.2.6. Will be removed in 3.0. Please use {@link #store(Publisher, String, String, Object)}
* instead.
*/
@Deprecated
Mono<ObjectId> store(AsyncInputStream content, @Nullable String filename, @Nullable String contentType,
@Nullable Object metadata);
@@ -151,7 +154,10 @@ public interface ReactiveGridFsOperations {
* @param metadata can be {@literal null}.
* @return a {@link Mono} emitting the {@link ObjectId} of the {@link com.mongodb.client.gridfs.model.GridFSFile} just
* created.
* @deprecated since 2.2.6. Will be removed in 3.0. Please use {@link #store(Publisher, String, String, Document)}
* instead.
*/
@Deprecated
Mono<ObjectId> store(AsyncInputStream content, @Nullable String filename, @Nullable String contentType,
@Nullable Document metadata);

View File

@@ -20,6 +20,7 @@ import java.util.stream.Collectors;
import org.bson.Document;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
@@ -71,6 +72,10 @@ public class StringBasedAggregation extends AbstractMongoQuery {
protected Object doExecute(MongoQueryMethod method, ResultProcessor resultProcessor,
ConvertingParameterAccessor accessor, Class<?> typeToRead) {
if (method.isPageQuery() || method.isSliceQuery()) {
throw new InvalidMongoDbApiUsageException(String.format("Repository aggregation method '%s' does not support '%s' return type. Please use eg. 'List' instead.", method.getName(), method.getReturnType().getType().getSimpleName()));
}
Class<?> sourceType = method.getDomainClass();
Class<?> targetType = typeToRead;

View File

@@ -231,7 +231,7 @@ class JsonScanner {
parenthesisCount--;
if (parenthesisCount == 0) {
buffer.read();
c = buffer.read();
break;
}
}

View File

@@ -333,6 +333,52 @@ public class DefaultBulkOperationsUnitTests {
.isEqualTo(new org.bson.Document("element", new Document("$gte", 100)));
}
@Test // DATAMONGO-2502
public void shouldRetainNestedArrayPathWithPlaceholdersForNoMatchingPaths() {
ops.updateOne(new BasicQuery("{}"), new Update().set("items.$.documents.0.fileId", "new-id")).execute();
verify(collection).bulkWrite(captor.capture(), any());
UpdateOneModel<Document> updateModel = (UpdateOneModel<Document>) captor.getValue().get(0);
assertThat(updateModel.getUpdate())
.isEqualTo(new Document("$set", new Document("items.$.documents.0.fileId", "new-id")));
}
@Test // DATAMONGO-2502
public void shouldRetainNestedArrayPathWithPlaceholdersForMappedEntity() {
DefaultBulkOperations ops = new DefaultBulkOperations(template, "collection-1",
new BulkOperationContext(BulkMode.ORDERED, Optional.of(mappingContext.getPersistentEntity(OrderTest.class)),
new QueryMapper(converter), new UpdateMapper(converter), null, null));
ops.updateOne(new BasicQuery("{}"), Update.update("items.$.documents.0.fileId", "file-id")).execute();
verify(collection).bulkWrite(captor.capture(), any());
UpdateOneModel<Document> updateModel = (UpdateOneModel<Document>) captor.getValue().get(0);
assertThat(updateModel.getUpdate())
.isEqualTo(new Document("$set", new Document("items.$.documents.0.the_file_id", "file-id")));
}
static class OrderTest {
String id;
List<OrderTestItem> items;
}
static class OrderTestItem {
private String cartId;
private List<OrderTestDocument> documents;
}
static class OrderTestDocument {
@Field("the_file_id")
private String fileId;
}
class SomeDomainType {
@Id String id;

View File

@@ -1499,6 +1499,15 @@ public class ProjectionOperationUnitTests {
assertThat(agg).isEqualTo(Document.parse("{ $project: { eq250: { $eq: [\"$qty\", 250]} } }"));
}
@Test // DATAMONGO-2513
public void shouldRenderEqAggregationExpressionWithListComparison() {
Document agg = project().and(ComparisonOperators.valueOf("qty").equalToValue(Arrays.asList(250))).as("eq250")
.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg).isEqualTo(Document.parse("{ $project: { eq250: { $eq: [\"$qty\", [250]]} } }"));
}
@Test // DATAMONGO-1536
public void shouldRenderGtAggregationExpression() {

View File

@@ -88,6 +88,7 @@ import com.mongodb.DBRef;
* @author Patrik Wasik
* @author Christoph Strobl
* @author Mark Paluch
* @author Heesu Jung
*/
@RunWith(MockitoJUnitRunner.class)
public class MappingMongoConverterUnitTests {
@@ -2078,6 +2079,23 @@ public class MappingMongoConverterUnitTests {
.isEqualTo(new BasicDBObject("property", "value"));
}
@Test // DATAMONGO-2300
public void readAndConvertDBRefNestedByMapCorrectly() {
org.bson.Document cluster = new org.bson.Document("_id", 100L);
DBRef dbRef = new DBRef("clusters", 100L);
org.bson.Document data = new org.bson.Document("_id", 3L);
data.append("cluster", dbRef);
MappingMongoConverter spyConverter = spy(converter);
Mockito.doReturn(cluster).when(spyConverter).readRef(dbRef);
Map<Object, Object> result = spyConverter.readMap(ClassTypeInformation.MAP, data, ObjectPath.ROOT);
assertThat(((LinkedHashMap) result.get("cluster")).get("_id")).isEqualTo(100L);
}
static class GenericType<T> {
T content;
}

View File

@@ -55,6 +55,7 @@ import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.TextQuery;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
@@ -927,6 +928,106 @@ public class QueryMapperUnitTests {
assertThat(target).isEqualTo(org.bson.Document.parse("{\"_id\": {\"$in\": [{\"$oid\": \"" + id + "\"}]}}"));
}
@Test // DATAMONGO-2488
public void mapsNestedArrayPathCorrectlyForNonMatchingPath() {
org.bson.Document target = mapper.getMappedObject(
query(where("array.$[some_item].nested.$[other_item]").is("value")).getQueryObject(),
context.getPersistentEntity(Foo.class));
assertThat(target).isEqualTo(new org.bson.Document("array.$[some_item].nested.$[other_item]", "value"));
}
@Test // DATAMONGO-2488
public void mapsNestedArrayPathCorrectlyForObjectTargetArray() {
org.bson.Document target = mapper.getMappedObject(
query(where("arrayObj.$[some_item].nested.$[other_item]").is("value")).getQueryObject(),
context.getPersistentEntity(WithNestedArray.class));
assertThat(target).isEqualTo(new org.bson.Document("arrayObj.$[some_item].nested.$[other_item]", "value"));
}
@Test // DATAMONGO-2488
public void mapsNestedArrayPathCorrectlyForStringTargetArray() {
org.bson.Document target = mapper.getMappedObject(
query(where("arrayString.$[some_item].nested.$[other_item]").is("value")).getQueryObject(),
context.getPersistentEntity(WithNestedArray.class));
assertThat(target).isEqualTo(new org.bson.Document("arrayString.$[some_item].nested.$[other_item]", "value"));
}
@Test // DATAMONGO-2488
public void mapsCustomFieldNamesForNestedArrayPathCorrectly() {
org.bson.Document target = mapper.getMappedObject(
query(where("arrayCustomName.$[some_item].nested.$[other_item]").is("value")).getQueryObject(),
context.getPersistentEntity(WithNestedArray.class));
assertThat(target).isEqualTo(new org.bson.Document("arrayCustomName.$[some_item].nes-ted.$[other_item]", "value"));
}
@Test // DATAMONGO-2502
public void shouldAllowDeeplyNestedPlaceholders() {
org.bson.Document target = mapper.getMappedObject(
query(where("level0.$[some_item].arrayObj.$[other_item].nested").is("value")).getQueryObject(),
context.getPersistentEntity(WithDeepArrayNesting.class));
assertThat(target).isEqualTo(new org.bson.Document("level0.$[some_item].arrayObj.$[other_item].nested", "value"));
}
@Test // DATAMONGO-2502
public void shouldAllowDeeplyNestedPlaceholdersWithCustomName() {
org.bson.Document target = mapper.getMappedObject(
query(where("level0.$[some_item].arrayCustomName.$[other_item].nested").is("value")).getQueryObject(),
context.getPersistentEntity(WithDeepArrayNesting.class));
assertThat(target)
.isEqualTo(new org.bson.Document("level0.$[some_item].arrayCustomName.$[other_item].nes-ted", "value"));
}
@Test // DATAMONGO-2517
public void shouldParseNestedKeywordWithArgumentMatchingTheSourceEntitiesConstructorCorrectly() {
TextQuery source = new TextQuery("test");
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WithSingleStringArgConstructor.class));
assertThat(target).isEqualTo(org.bson.Document.parse("{\"$text\" : { \"$search\" : \"test\" }}"));
}
class WithDeepArrayNesting {
List<WithNestedArray> level0;
}
class WithNestedArray {
List<NestedArrayOfObj> arrayObj;
List<NestedArrayOfString> arrayString;
List<NestedArrayOfObjCustomFieldName> arrayCustomName;
}
class NestedArrayOfObj {
List<ArrayObj> nested;
}
class NestedArrayOfObjCustomFieldName {
@Field("nes-ted") List<ArrayObj> nested;
}
class NestedArrayOfString {
List<String> nested;
}
class ArrayObj {
String foo;
}
@Document
public class Foo {
@Id private ObjectId id;
@@ -1069,4 +1170,16 @@ public class QueryMapperUnitTests {
String id;
@Field(targetType = FieldType.OBJECT_ID) String stringAsOid;
}
@Document
static class WithSingleStringArgConstructor {
String value;
public WithSingleStringArgConstructor() {}
public WithSingleStringArgConstructor(String value) {
this.value = value;
}
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.data.mongodb.core.DocumentTestUtils;
* Unit tests for {@link TextCriteria}.
*
* @author Christoph Strobl
* @author Daniel Debray
*/
public class TextCriteriaUnitTests {
@@ -33,6 +34,7 @@ public class TextCriteriaUnitTests {
public void shouldNotHaveLanguageField() {
TextCriteria criteria = TextCriteria.forDefaultLanguage();
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ }"));
}
@@ -40,6 +42,7 @@ public class TextCriteriaUnitTests {
public void shouldNotHaveLanguageForNonDefaultLanguageField() {
TextCriteria criteria = TextCriteria.forLanguage("spanish");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$language\" : \"spanish\" }"));
}
@@ -47,6 +50,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldForSingleTermCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"cake\" }"));
}
@@ -54,6 +58,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldCorrectlyForMultipleTermsCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("bake", "coffee", "cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"bake coffee cake\" }"));
}
@@ -61,6 +66,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldForPhraseCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingPhrase("coffee cake");
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "\"coffee cake\""));
}
@@ -69,6 +75,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateNotFieldCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatching("cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"-cake\" }"));
}
@@ -76,6 +83,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldCorrectlyForNotMultipleTermsCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingAny("bake", "coffee", "cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"-bake -coffee -cake\" }"));
}
@@ -83,6 +91,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldForNotPhraseCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingPhrase("coffee cake");
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "-\"coffee cake\""));
}
@@ -91,6 +100,7 @@ public class TextCriteriaUnitTests {
public void caseSensitiveOperatorShouldBeSetCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").caseSensitive(true);
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "coffee").append("$caseSensitive", true));
}
@@ -99,10 +109,23 @@ public class TextCriteriaUnitTests {
public void diacriticSensitiveOperatorShouldBeSetCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").diacriticSensitive(true);
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "coffee").append("$diacriticSensitive", true));
}
@Test // DATAMONGO-2504
public void twoIdenticalCriteriaShouldBeEqual() {
TextCriteria criteriaOne = TextCriteria.forDefaultLanguage().matching("coffee");
TextCriteria criteriaTwo = TextCriteria.forDefaultLanguage().matching("coffee");
assertThat(criteriaOne).isEqualTo(criteriaTwo);
assertThat(criteriaOne).hasSameHashCodeAs(criteriaTwo);
assertThat(criteriaOne).isNotEqualTo(criteriaTwo.diacriticSensitive(false));
assertThat(criteriaOne.hashCode()).isNotEqualTo(criteriaTwo.diacriticSensitive(false).hashCode());
}
private Document searchObject(String json) {
return new Document("$text", Document.parse(json));
}

View File

@@ -34,8 +34,12 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
@@ -198,6 +202,16 @@ public class StringBasedAggregationUnitTests {
assertThat(collationOf(invocation)).isEqualTo(Collation.of("en_US"));
}
@Test // DATAMONGO-2506
public void aggregateRaisesErrorOnInvalidReturnType() {
StringBasedAggregation sba = createAggregationForMethod("invalidPageReturnType", Pageable.class);
assertThatExceptionOfType(InvalidMongoDbApiUsageException.class) //
.isThrownBy(() -> sba.execute(new Object[] { PageRequest.of(0, 1) })) //
.withMessageContaining("invalidPageReturnType") //
.withMessageContaining("Page");
}
private AggregationInvocation executeAggregation(String name, Object... args) {
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
@@ -276,6 +290,9 @@ public class StringBasedAggregationUnitTests {
@Aggregation(pipeline = RAW_GROUP_BY_LASTNAME_STRING, collation = "de_AT")
PersonAggregate aggregateWithCollation(Collation collation);
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
Page<Person> invalidPageReturnType(Pageable page);
}
static class PersonAggregate {

View File

@@ -231,7 +231,7 @@ public class ParameterBindingJsonReaderUnitTests {
public void bindMultipleUnquotedParameterInArray() {
Document target = parse("{ 'name' : { $in : [?0,?1] } }", "dalinar", "kohlin");
assertThat(target).isEqualTo(new Document("name", new Document("$in",Arrays.asList("dalinar", "kohlin"))));
assertThat(target).isEqualTo(new Document("name", new Document("$in", Arrays.asList("dalinar", "kohlin"))));
}
@Test // DATAMONGO-2476
@@ -252,7 +252,16 @@ public class ParameterBindingJsonReaderUnitTests {
public void bindQuotedMulitParameterInArray() {
Document target = parse("{ 'name' : { $in : ['?0,?1'] } }", "dalinar", "kohlin");
assertThat(target).isEqualTo(new Document("name", new Document("$in", Collections.singletonList("dalinar,kohlin"))));
assertThat(target)
.isEqualTo(new Document("name", new Document("$in", Collections.singletonList("dalinar,kohlin"))));
}
@Test // DATAMONGO-2523
public void bindSpelExpressionInArrayCorrectly/* closing bracket must not have leading whitespace! */() {
Document target = parse("{ $and : [?#{ [0] == null ? { '$where' : 'true' } : { 'v1' : { '$in' : {[0]} } } }]}", 1);
assertThat(target).isEqualTo(Document.parse("{\"$and\": [{\"v1\": {\"$in\": [1]}}]}"));
}
private static Document parse(String json, Object... args) {

View File

@@ -416,7 +416,7 @@ The MappingMongoConverter can use metadata to drive the mapping of objects to do
* `@TextIndexed`: Applied at the field level to mark the field to be included in the text index.
* `@HashIndexed`: Applied at the field level for usage within a hashed index to partition data across a sharded cluster.
* `@Language`: Applied at the field level to set the language override property for text index.
* `@Transient`: By default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database
* `@Transient`: By default, all fields are mapped to the document. This annotation excludes the field where it is applied from being stored in the database. Transient properties cannot be used within a persistence constructor as the converter cannot materialize a value for the constructor argument.
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Value`: This annotation is part of the Spring Framework . Within the mapping framework it can be applied to constructor arguments. This lets you use a Spring Expression Language statement to transform a key's value retrieved in the database before it is used to construct a domain object. In order to reference a property of a given document one has to use expressions like: `@Value("#root.myProperty")` where `root` refers to the root of the given document.
* `@Field`: Applied at the field level it allows to describe the name and type of the field as it will be represented in the MongoDB BSON document thus allowing the name and type to be different than the fieldname of the class as well as the property type.

View File

@@ -1,6 +1,66 @@
Spring Data MongoDB Changelog
=============================
Changes in version 2.2.7.RELEASE (2020-04-28)
---------------------------------------------
* DATAMONGO-2529 - EntityReader called with null argument.
* DATAMONGO-2523 - ParameterBindingJsonReader skips too many chars.
* DATAMONGO-2517 - Text search fails on entity with second constructor.
* DATAMONGO-2513 - ComparisonOperators.Eq#equalToValue doesn't work well with Lists.
* DATAMONGO-2506 - StringBasedAggregation should raise meaningful error when unsupported return type requested.
* DATAMONGO-2504 - TextCriteria Hashcode and equals.
* DATAMONGO-2502 - Regression in Update property mapping.
* DATAMONGO-2500 - Release 2.2.7 (Moore SR7).
Changes in version 2.1.17.RELEASE (2020-04-28)
----------------------------------------------
* DATAMONGO-2529 - EntityReader called with null argument.
* DATAMONGO-2497 - Update documentation regarding @Transient properties usage in the persistence constructor.
* DATAMONGO-2484 - Release 2.1.17 (Lovelace SR17).
* DATAMONGO-2300 - Can't read and convert DBRef when the type is Map.
Changes in version 3.0.0.RC1 (2020-03-31)
-----------------------------------------
* DATAMONGO-2501 - Upgrade to Querydsl 4.3.
* DATAMONGO-2498 - Upgrade to MongoDB 4.0.1 Drivers.
* DATAMONGO-2497 - Update documentation regarding @Transient properties usage in the persistence constructor.
* DATAMONGO-2492 - Release 3.0 RC1 (Neumann).
* DATAMONGO-2488 - KeyMapper.mapPropertyName does not work for nested arrays.
* DATAMONGO-2479 - More EntityCallback specializations.
* DATAMONGO-2477 - Disable auto-index creation by default.
* DATAMONGO-2475 - QueryDSL: $and and $or with more than two arguments in final query document.
* DATAMONGO-2416 - Add default methods accepting CriteriaDefinition on Fluent API.
* DATAMONGO-2300 - Can't read and convert DBRef when the type is Map.
* DATAMONGO-1026 - Joda, JSR-310 and ThreeTenBp converters are timezone-sensitive.
* DATAMONGO-931 - Add support for $redact operation.
* DATAMONGO-625 - GridFsOperations.createFile method should allow specification of _id.
Changes in version 2.2.6.RELEASE (2020-03-25)
---------------------------------------------
* DATAMONGO-2497 - Update documentation regarding @Transient properties usage in the persistence constructor.
* DATAMONGO-2488 - KeyMapper.mapPropertyName does not work for nested arrays.
* DATAMONGO-2485 - Release 2.2.6 (Moore SR6).
* DATAMONGO-2445 - Deprecate ReactiveGridFs methods using AsyncInputStream.
* DATAMONGO-2300 - Can't read and convert DBRef when the type is Map.
Changes in version 3.0.0.M4 (2020-03-11)
----------------------------------------
* DATAMONGO-2491 - Adapt to Mockito 3.3 changes.
* DATAMONGO-2489 - Upgrade to MongoDB Driver 4.0.
* DATAMONGO-2481 - Speed up build.
* DATAMONGO-2478 - NPE when using Query annotation and with sort and pageable.
* DATAMONGO-2476 - JsonParseException: JSON reader was expecting a value but found '}'.
* DATAMONGO-2474 - Upgrade to MongoDB Driver 4.0.0-rc0.
* DATAMONGO-2473 - Release 3.0 M4 (Neumann).
* DATAMONGO-2363 - Add support for $merge aggregation stage.
* DATAMONGO-2355 - Revise Abstract…MongoConfiguration to expose more bean detail and avoid proxying.
* DATAMONGO-2341 - Support shard key derivation.
Changes in version 2.2.5.RELEASE (2020-02-26)
---------------------------------------------
* DATAMONGO-2478 - NPE when using Query annotation and with sort and pageable.
@@ -2935,3 +2995,8 @@ Repository

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 2.2.5
Spring Data MongoDB 2.2.7
Copyright (c) [2010-2019] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -11,3 +11,5 @@ conditions of the subcomponent's license, as noted in the LICENSE file.