Compare commits

..

26 Commits

Author SHA1 Message Date
Christoph Strobl
1a77b1bc56 Release version 4.0 M4 (2022.0.0).
See #4005
2022-05-13 10:43:59 +02:00
Christoph Strobl
140fb2e9ea Prepare 4.0 M4 (2022.0.0).
See #4005
2022-05-13 10:43:20 +02:00
Jay Bryant
b571c8958d Editing pass for new content in reference documentation.
Closes: #4049
2022-05-11 05:38:24 +02:00
Christoph Strobl
8d54cae54d Polishing.
Update Query javadoc.

Original Pull Request: #3999
2022-05-10 16:33:19 +02:00
Raul Mello Silva
14a71f0498 Update Query.limit javadoc.
This commit explains usage of Query.limit(int), which will be set to unlimited when set to zero or a negative value.

Closes: #3999
2022-05-10 16:19:07 +02:00
Christoph Strobl
14c265f3a1 Provide additional meta information via pom.xml
Add scm & issueManagement.

Closes: #4048
2022-05-10 12:31:29 +02:00
nniesen
440a289ac6 Update spring.io project urls.
This commit updates outdated projects.spring.io links to spring.io/projects.

Closes: #4042
2022-05-09 13:57:49 +02:00
John Blum
9663a2227b Adapt to API changes in PropertyValueConverters.
Closes #4040.
2022-05-02 17:19:17 -07:00
Mark Paluch
b134e1916d Upgrade to MongoDB driver 4.6.0.
Closes #4027
2022-04-19 10:05:37 +02:00
Greg L. Turnquist
65b02f92b4 Use updated coordinates for Hibernate Validator.
See #4024.
2022-04-15 10:45:58 -05:00
Greg L. Turnquist
667b71e073 Switch to Micrometer 1.10's tracing APIs.
Micrometer Tracing 1.10 has some breaking APIs.

See #4023.
2022-04-15 10:04:41 -05:00
Mark Paluch
225dbee15f Simplify dependency version arrangement.
We now inherit the version number and repositories from the parent pom.

See #4017
2022-04-07 09:53:10 +02:00
Mark Paluch
c04ceb163b Polishing.
Reformat code.

See #4017
2022-04-07 09:44:42 +02:00
Greg L. Turnquist
711ac343fe Fix Micrometer-based deployment issues.
When deploying to artifactory, a Micrometer-based plugin can't be found.

See #4017.
2022-04-06 09:25:44 -05:00
Mark Paluch
852a4ecc59 Polishing.
Refine default conversions creation.

See #4014
Original pull request: #4015.
2022-04-05 10:07:51 +02:00
Christoph Strobl
7ab2428c64 Make sure to initialize PropvertyValueConversions in Converter setup.
Closes #4014
Original pull request: #4015.
2022-04-05 10:07:46 +02:00
Oliver Drotbohm
350acf66bc Adapt to API changes in Spring Data Commons.
spring-projects/spring-data-commons#2518 introduced TypeInformation.getTypeDescriptor() which we need to implement in our custom FieldTypeInformation.
2022-04-04 18:21:04 +02:00
Christoph Strobl
ab94a94b2e Upgrade to MongoDB driver 4.5.1
Resolves: #4013
2022-04-04 10:20:20 +02:00
Christoph Strobl
4c77763cd3 Introduce Observability with Micrometer and Micrometer Tracing.
See #3942.
2022-03-29 13:09:07 -05:00
Christoph Strobl
f197953480 Update build triggers.
See: #4005
2022-03-24 13:53:25 +01:00
Mark Paluch
44afd4939e After release cleanups.
See #4003
2022-03-22 14:07:38 +01:00
Mark Paluch
575917435e Prepare next development iteration.
See #4003
2022-03-22 14:07:36 +01:00
Mark Paluch
2db55ab0aa Release version 4.0 M3 (2022.0.0).
See #4003
2022-03-22 14:00:23 +01:00
Mark Paluch
79602b7dbe Prepare 4.0 M3 (2022.0.0).
See #4003
2022-03-22 14:00:02 +01:00
Mark Paluch
d5d2371b9e After release cleanups.
See #3937
2022-03-21 16:44:41 +01:00
Mark Paluch
c95e8a5748 Prepare next development iteration.
See #3937
2022-03-21 16:44:39 +01:00
28 changed files with 2082 additions and 534 deletions

2
Jenkinsfile vendored
View File

@@ -9,7 +9,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/3.0.x", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
}
options {

View File

@@ -1,8 +1,8 @@
image:https://spring.io/badges/spring-data-mongodb/ga.svg[Spring Data MongoDB,link=https://projects.spring.io/spring-data-mongodb#quick-start] image:https://spring.io/badges/spring-data-mongodb/snapshot.svg[Spring Data MongoDB,link=https://projects.spring.io/spring-data-mongodb#quick-start]
image:https://spring.io/badges/spring-data-mongodb/ga.svg[Spring Data MongoDB,link=https://spring.io/projects/spring-data-mongodb#quick-start] image:https://spring.io/badges/spring-data-mongodb/snapshot.svg[Spring Data MongoDB,link=https://spring.io/projects/spring-data-mongodb#quick-start]
= Spring Data MongoDB image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The primary goal of the https://spring.io/projects/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The Spring Data MongoDB project aims to provide a familiar and consistent Spring-based programming model for new datastores while retaining store-specific features and capabilities.
The Spring Data MongoDB project provides integration with the MongoDB document database.

484
pom.xml
View File

@@ -1,164 +1,326 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-M2</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
<description>MongoDB support for Spring Data</description>
<url>https://projects.spring.io/spring-data-mongodb</url>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.0.0-M2</version>
</parent>
<modules>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-distribution</module>
</modules>
<properties>
<source.level>16</source.level>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>3.0.0-M2</springdata.commons>
<mongo>4.5.0</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
</properties>
<developers>
<developer>
<id>ogierke</id>
<name>Oliver Gierke</name>
<email>ogierke at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Project Lead</role>
</roles>
<timezone>+1</timezone>
</developer>
<developer>
<id>trisberg</id>
<name>Thomas Risberg</name>
<email>trisberg at vmware.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>-5</timezone>
</developer>
<developer>
<id>mpollack</id>
<name>Mark Pollack</name>
<email>mpollack at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>-5</timezone>
</developer>
<developer>
<id>jbrisbin</id>
<name>Jon Brisbin</name>
<email>jbrisbin at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>-6</timezone>
</developer>
<developer>
<id>tdarimont</id>
<name>Thomas Darimont</name>
<email>tdarimont at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
<developer>
<id>cstrobl</id>
<name>Christoph Strobl</name>
<email>cstrobl at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
<developer>
<id>mpaluch</id>
<name>Mark Paluch</name>
<email>mpaluch at pivotal.io</email>
<organization>Pivotal</organization>
<organizationUrl>https://www.pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
</developers>
<profiles>
<profile>
<id>benchmarks</id>
<modules>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-distribution</module>
<module>spring-data-mongodb-benchmarks</module>
</modules>
</profile>
</profiles>
<dependencies>
<!-- MongoDB -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>${mongo}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
<repository>
<id>sonatype-libs-snapshot</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-plugins-release</id>
<url>https://repo.spring.io/plugins-release</url>
</pluginRepository>
<pluginRepository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</pluginRepository>
</pluginRepositories>
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-M4</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
<description>MongoDB support for Spring Data</description>
<url>https://spring.io/projects/spring-data-mongodb</url>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.0.0-M4</version>
</parent>
<modules>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-distribution</module>
</modules>
<properties>
<source.level>16</source.level>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>3.0.0-M4</springdata.commons>
<mongo>4.6.0</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
</properties>
<developers>
<developer>
<id>ogierke</id>
<name>Oliver Gierke</name>
<email>ogierke at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Project Lead</role>
</roles>
<timezone>+1</timezone>
</developer>
<developer>
<id>trisberg</id>
<name>Thomas Risberg</name>
<email>trisberg at vmware.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>-5</timezone>
</developer>
<developer>
<id>mpollack</id>
<name>Mark Pollack</name>
<email>mpollack at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>-5</timezone>
</developer>
<developer>
<id>jbrisbin</id>
<name>Jon Brisbin</name>
<email>jbrisbin at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>-6</timezone>
</developer>
<developer>
<id>tdarimont</id>
<name>Thomas Darimont</name>
<email>tdarimont at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
<developer>
<id>cstrobl</id>
<name>Christoph Strobl</name>
<email>cstrobl at gopivotal.com</email>
<organization>Pivotal</organization>
<organizationUrl>https://pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
<developer>
<id>mpaluch</id>
<name>Mark Paluch</name>
<email>mpaluch at pivotal.io</email>
<organization>Pivotal</organization>
<organizationUrl>https://www.pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
<timezone>+1</timezone>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/spring-projects/spring-data-mongodb.git</connection>
<developerConnection>scm:git:git@github.com:spring-projects/spring-data-mongodb.git</developerConnection>
<url>https://github.com/spring-projects/spring-data-mongodb</url>
</scm>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/spring-projects/spring-data-mongodb/issues</url>
</issueManagement>
<profiles>
<profile>
<id>benchmarks</id>
<modules>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-distribution</module>
<module>spring-data-mongodb-benchmarks</module>
</modules>
</profile>
</profiles>
<dependencies>
<!-- MongoDB -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>${mongo}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>sonatype-libs-snapshot</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-plugins-release</id>
<url>https://repo.spring.io/plugins-release</url>
</pluginRepository>
<pluginRepository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-M2</version>
<version>4.0.0-M4</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -14,13 +15,18 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-M2</version>
<version>4.0.0-M4</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<project.root>${basedir}/..</project.root>
<dist.key>SDMONGO</dist.key>
<!-- Observability -->
<micrometer-docs-generator.inputPath>${maven.multiModuleProjectDirectory}/spring-data-mongodb/</micrometer-docs-generator.inputPath>
<micrometer-docs-generator.inclusionPattern>.*</micrometer-docs-generator.inclusionPattern>
<micrometer-docs-generator.outputPath>${maven.multiModuleProjectDirectory}/target/</micrometer-docs-generator.outputPath>
</properties>
<build>
@@ -29,12 +35,63 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-metrics-metadata</id>
<phase>prepare-package</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>io.micrometer.docs.metrics.DocsFromSources
</mainClass>
</configuration>
</execution>
<execution>
<id>generate-tracing-metadata</id>
<phase>prepare-package</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>io.micrometer.docs.spans.DocsFromSources
</mainClass>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-docs-generator-spans</artifactId>
<version>${micrometer-docs-generator}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-docs-generator-metrics</artifactId>
<version>${micrometer-docs-generator}</version>
<type>jar</type>
</dependency>
</dependencies>
<configuration>
<includePluginDependencies>true</includePluginDependencies>
<arguments>
<argument>${micrometer-docs-generator.inputPath}</argument>
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
<argument>${micrometer-docs-generator.outputPath}</argument>
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<configuration>
<attributes>
<mongo-reactivestreams>${mongo.reactivestreams}</mongo-reactivestreams>
<mongo-reactivestreams>${mongo.reactivestreams}
</mongo-reactivestreams>
<reactor>${reactor}</reactor>
</attributes>
</configuration>

View File

@@ -1,361 +1,391 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-mongodb</artifactId>
<artifactId>spring-data-mongodb</artifactId>
<name>Spring Data MongoDB - Core</name>
<description>MongoDB support for Spring Data</description>
<name>Spring Data MongoDB - Core</name>
<description>MongoDB support for Spring Data</description>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-M2</version>
<relativePath>../pom.xml</relativePath>
</parent>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-M4</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<objenesis>1.3</objenesis>
<equalsverifier>1.7.8</equalsverifier>
<java-module-name>spring.data.mongodb</java-module-name>
<project.root>${basedir}/..</project.root>
<multithreadedtc>1.01</multithreadedtc>
</properties>
<properties>
<objenesis>1.3</objenesis>
<equalsverifier>1.7.8</equalsverifier>
<java-module-name>spring.data.mongodb</java-module-name>
<project.root>${basedir}/..</project.root>
<multithreadedtc>1.01</multithreadedtc>
</properties>
<dependencies>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<!-- Spring Data -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${springdata.commons}</version>
</dependency>
<!-- Spring Data -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${springdata.commons}</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>${querydsl}</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>${querydsl}</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<optional>true</optional>
</dependency>
<!-- reactive -->
<!-- reactive -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>${mongo}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>${mongo}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-reactivestreams</artifactId>
<version>${mongo.reactivestreams}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-reactivestreams</artifactId>
<version>${mongo.reactivestreams}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava3}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava3}</version>
<optional>true</optional>
</dependency>
<!-- CDI -->
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
<!-- CDI -->
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>${cdi}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>${cdi}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta-annotation-api}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta-annotation-api}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-spi</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-spi</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-impl</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-impl</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<!-- JSR 303 Validation -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${validation}</version>
<optional>true</optional>
</dependency>
<!-- JSR 303 Validation -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${validation}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>${objenesis}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>${objenesis}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.1.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.1.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>${equalsverifier}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>de.schauderhaft.degraph</groupId>
<artifactId>degraph-check</artifactId>
<version>0.1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>${equalsverifier}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>edu.umd.cs.mtc</groupId>
<artifactId>multithreadedtc</artifactId>
<version>${multithreadedtc}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>0.5.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.schauderhaft.degraph</groupId>
<artifactId>degraph-check</artifactId>
<version>0.1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>edu.umd.cs.mtc</groupId>
<artifactId>multithreadedtc</artifactId>
<version>${multithreadedtc}</version>
<scope>test</scope>
</dependency>
<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>0.5.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<optional>true</optional>
</dependency>
<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>${mockk}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<optional>true</optional>
</dependency>
<!-- jMolecules -->
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jmolecules</groupId>
<artifactId>jmolecules-ddd</artifactId>
<version>${jmolecules}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>${mockk}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId>
<scope>test</scope>
</dependency>
<build>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-integration-test</artifactId>
<scope>test</scope>
</dependency>
<plugins>
<!-- jMolecules -->
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>${apt}</version>
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl}</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>generate-test-sources</phase>
<goals>
<goal>test-process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-test-sources</outputDirectory>
<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
<dependency>
<groupId>org.jmolecules</groupId>
<artifactId>jmolecules-ddd</artifactId>
<version>${jmolecules}</version>
<scope>test</scope>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<useFile>false</useFile>
<includes>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/PerformanceTests.java</exclude>
<exclude>**/ReactivePerformanceTests.java</exclude>
</excludes>
<systemPropertyVariables>
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
<reactor.trace.cancel>true</reactor.trace.cancel>
</systemPropertyVariables>
</configuration>
</plugin>
</dependencies>
</plugins>
<build>
</build>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>${apt}</version>
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl}</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>generate-test-sources</phase>
<goals>
<goal>test-process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-test-sources</outputDirectory>
<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<useFile>false</useFile>
<includes>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/PerformanceTests.java</exclude>
<exclude>**/ReactivePerformanceTests.java</exclude>
</excludes>
<systemPropertyVariables>
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
<reactor.trace.cancel>true</reactor.trace.cancel>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -35,6 +35,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.annotation.Reference;
import org.springframework.data.convert.CustomConversions;
@@ -935,9 +936,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
TypeInformation<?> type = prop.getTypeInformation();
if (conversions.hasPropertyValueConverter(prop)) {
if (conversions.hasValueConverter(prop)) {
accessor.put(prop,
conversions.getPropertyValueConverter(prop).write(obj, new MongoConversionContext(prop, this)));
conversions.getPropertyValueConversions().getValueConverter(prop)
.write(obj, new MongoConversionContext(prop, this)));
return;
}
@@ -1271,9 +1273,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private void writeSimpleInternal(@Nullable Object value, Bson bson, MongoPersistentProperty property) {
DocumentAccessor accessor = new DocumentAccessor(bson);
if (conversions.hasPropertyValueConverter(property)) {
if (conversions.hasValueConverter(property)) {
accessor.put(property,
conversions.getPropertyValueConverter(property).write(value, new MongoConversionContext(property, this)));
conversions.getPropertyValueConversions().getValueConverter(property)
.write(value, new MongoConversionContext(property, this)));
return;
}
@@ -1918,8 +1921,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return null;
}
if (context.conversions.hasPropertyValueConverter(property)) {
return (T) context.conversions.getPropertyValueConverter(property).read(value,
if (context.conversions.hasValueConverter(property)) {
return (T) context.conversions.getPropertyValueConversions().getValueConverter(property).read(value,
new MongoConversionContext(property, context.sourceConverter));
}
@@ -2131,6 +2134,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
public org.springframework.data.util.TypeInformation<? extends S> specialize(ClassTypeInformation type) {
return delegate.specialize(type);
}
@Override
public TypeDescriptor toTypeDescriptor() {
return delegate.toTypeDescriptor();
}
}
/**

View File

@@ -156,7 +156,8 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
private boolean useNativeDriverJavaTimeCodecs = false;
private final List<Object> customConverters = new ArrayList<>();
private PropertyValueConversions propertyValueConversions = new SimplePropertyValueConversions();
private final PropertyValueConversions internalValueConversion = PropertyValueConversions.simple(it -> {});
private PropertyValueConversions propertyValueConversions = internalValueConversion;
/**
* Create a {@link MongoConverterConfigurationAdapter} using the provided {@code converters} and our own codecs for
@@ -177,7 +178,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
}
/**
* Set whether or not to use the native MongoDB Java Driver {@link org.bson.codecs.Codec codes} for
* Set whether to or not to use the native MongoDB Java Driver {@link org.bson.codecs.Codec codes} for
* {@link org.bson.codecs.jsr310.LocalDateCodec LocalDate}, {@link org.bson.codecs.jsr310.LocalTimeCodec LocalTime}
* and {@link org.bson.codecs.jsr310.LocalDateTimeCodec LocalDateTime} using a {@link ZoneOffset#UTC}.
*
@@ -317,7 +318,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
PropertyValueConversions valueConversions() {
if (this.propertyValueConversions == null) {
this.propertyValueConversions = new SimplePropertyValueConversions();
this.propertyValueConversions = internalValueConversion;
}
return this.propertyValueConversions;
@@ -325,6 +326,11 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
ConverterConfiguration createConverterConfiguration() {
if (hasDefaultPropertyValueConversions()
&& propertyValueConversions instanceof SimplePropertyValueConversions svc) {
svc.init();
}
if (!useNativeDriverJavaTimeCodecs) {
return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true,
this.propertyValueConversions);
@@ -381,5 +387,9 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
return DateToUtcLocalDateTimeConverter.INSTANCE.convert(source).toLocalDate();
}
}
private boolean hasDefaultPropertyValueConversions() {
return propertyValueConversions == internalValueConversion;
}
}
}

View File

@@ -432,8 +432,9 @@ public class QueryMapper {
Object value = applyFieldTargetTypeHintToValue(documentField, sourceValue);
if(documentField.getProperty() != null && converter.getCustomConversions().hasPropertyValueConverter(documentField.getProperty())) {
return converter.getCustomConversions().getPropertyValueConverter(documentField.getProperty()).write(value, new MongoConversionContext(documentField.getProperty(), converter));
if(documentField.getProperty() != null && converter.getCustomConversions().hasValueConverter(documentField.getProperty())) {
return converter.getCustomConversions().getPropertyValueConversions().getValueConverter(documentField.getProperty())
.write(value, new MongoConversionContext(documentField.getProperty(), converter));
}
if (documentField.isIdField() && !documentField.isAssociation()) {

View File

@@ -122,9 +122,10 @@ public class Query {
}
/**
* Set number of documents to skip before returning results.
* Set number of documents to skip before returning results. Use {@literal zero} or a {@literal negative} value to
* avoid skipping.
*
* @param skip
* @param skip number of documents to skip. Use {@literal zero} or a {@literal negative} value to avoid skipping.
* @return this.
*/
public Query skip(long skip) {
@@ -133,9 +134,10 @@ public class Query {
}
/**
* Limit the number of returned documents to {@code limit}.
* Limit the number of returned documents to {@code limit}. A {@literal zero} or {@literal negative} value is
* considered as unlimited.
*
* @param limit
* @param limit number of documents to return. Use {@literal zero} or {@literal negative} for unlimited.
* @return this.
*/
public Query limit(int limit) {
@@ -312,7 +314,7 @@ public class Query {
}
/**
* Get the number of documents to skip.
* Get the number of documents to skip. {@literal Zero} or a {@literal negative} value indicates no skip.
*
* @return number of documents to skip
*/
@@ -321,7 +323,8 @@ public class Query {
}
/**
* Get the maximum number of documents to be return.
* Get the maximum number of documents to be return. {@literal Zero} or a {@literal negative} value indicates no
* limit.
*
* @return number of documents to return.
*/

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2013-2022 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.observability;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames;
import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ConnectionId;
import com.mongodb.event.CommandStartedEvent;
/**
* Default {@link MongoHandlerKeyValuesProvider} implementation.
*
* @author Greg Turnquist
* @since 4.0.0
*/
public class DefaultMongoHandlerKeyValuesProvider implements MongoHandlerKeyValuesProvider {
@Override
public KeyValues getLowCardinalityKeyValues(MongoHandlerContext context) {
KeyValues keyValues = KeyValues.empty();
if (context.getCollectionName() != null) {
keyValues = keyValues
.and(KeyValue.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.getKeyName(), context.getCollectionName()));
}
KeyValue connectionTag = connectionTag(context.getCommandStartedEvent());
if (connectionTag != null) {
keyValues = keyValues.and(connectionTag);
}
return keyValues;
}
@Override
public KeyValues getHighCardinalityKeyValues(MongoHandlerContext context) {
return KeyValues.of(KeyValue.of(HighCardinalityCommandKeyNames.MONGODB_COMMAND.getKeyName(),
context.getCommandStartedEvent().getCommandName()));
}
/**
* Extract connection details for a MongoDB connection into a {@link KeyValue}.
*
* @param event
* @return
*/
private static KeyValue connectionTag(CommandStartedEvent event) {
ConnectionDescription connectionDescription = event.getConnectionDescription();
if (connectionDescription != null) {
ConnectionId connectionId = connectionDescription.getConnectionId();
if (connectionId != null) {
return KeyValue.of(LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID.getKeyName(),
connectionId.getServerId().getClusterId().getValue());
}
}
return null;
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2013-2022 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.observability;
import io.micrometer.observation.Observation;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.springframework.lang.Nullable;
import com.mongodb.RequestContext;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
/**
* A {@link Observation.Context} that contains MongoDB events.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
public class MongoHandlerContext extends Observation.Context {
/**
* @see https://docs.mongodb.com/manual/reference/command for the command reference
*/
private static final Set<String> COMMANDS_WITH_COLLECTION_NAME = new LinkedHashSet<>(
Arrays.asList("aggregate", "count", "distinct", "mapReduce", "geoSearch", "delete", "find", "findAndModify",
"insert", "update", "collMod", "compact", "convertToCapped", "create", "createIndexes", "drop", "dropIndexes",
"killCursors", "listIndexes", "reIndex"));
private final CommandStartedEvent commandStartedEvent;
private final RequestContext requestContext;
private final String collectionName;
private CommandSucceededEvent commandSucceededEvent;
private CommandFailedEvent commandFailedEvent;
public MongoHandlerContext(CommandStartedEvent commandStartedEvent, RequestContext requestContext) {
this.commandStartedEvent = commandStartedEvent;
this.requestContext = requestContext;
this.collectionName = getCollectionName(commandStartedEvent);
}
public CommandStartedEvent getCommandStartedEvent() {
return this.commandStartedEvent;
}
public RequestContext getRequestContext() {
return this.requestContext;
}
public String getCollectionName() {
return this.collectionName;
}
public String getContextualName() {
if (this.collectionName == null) {
return this.commandStartedEvent.getCommandName();
}
return this.commandStartedEvent.getCommandName() + " " + this.collectionName;
}
public void setCommandSucceededEvent(CommandSucceededEvent commandSucceededEvent) {
this.commandSucceededEvent = commandSucceededEvent;
}
public void setCommandFailedEvent(CommandFailedEvent commandFailedEvent) {
this.commandFailedEvent = commandFailedEvent;
}
/**
* Transform the command name into a collection name;
*
* @param event the {@link CommandStartedEvent}
* @return the name of the collection based on the command
*/
@Nullable
private static String getCollectionName(CommandStartedEvent event) {
String commandName = event.getCommandName();
BsonDocument command = event.getCommand();
if (COMMANDS_WITH_COLLECTION_NAME.contains(commandName)) {
String collectionName = getNonEmptyBsonString(command.get(commandName));
if (collectionName != null) {
return collectionName;
}
}
// Some other commands, like getMore, have a field like {"collection": collectionName}.
return getNonEmptyBsonString(command.get("collection"));
}
/**
* Utility method to convert {@link BsonValue} into a plain string.
*
* @return trimmed string from {@code bsonValue} or null if the trimmed string was empty or the value wasn't a string
*/
@Nullable
private static String getNonEmptyBsonString(BsonValue bsonValue) {
if (bsonValue == null || !bsonValue.isString()) {
return null;
}
String stringValue = bsonValue.asString().getValue().trim();
return stringValue.isEmpty() ? null : stringValue;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2013-2022 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.observability;
import io.micrometer.observation.Observation;
/**
* {@link Observation.KeyValuesProvider} for {@link MongoHandlerContext}.
*
* @author Greg Turnquist
* @since 4.0.0
*/
public interface MongoHandlerKeyValuesProvider extends Observation.KeyValuesProvider<MongoHandlerContext> {
@Override
default boolean supportsContext(Observation.Context context) {
return context instanceof MongoHandlerContext;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2013-2022 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.observability;
import io.micrometer.common.docs.KeyName;
import io.micrometer.observation.docs.DocumentedObservation;
/**
* A MongoDB-based {@link io.micrometer.observation.Observation}.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
enum MongoObservation implements DocumentedObservation {
/**
* Timer created around a MongoDB command execution.
*/
MONGODB_COMMAND_OBSERVATION {
@Override
public String getName() {
return "spring.data.mongodb.command";
}
@Override
public KeyName[] getLowCardinalityKeyNames() {
return LowCardinalityCommandKeyNames.values();
}
@Override
public KeyName[] getHighCardinalityKeyNames() {
return HighCardinalityCommandKeyNames.values();
}
@Override
public String getPrefix() {
return "spring.data.mongodb";
}
};
/**
* Enums related to low cardinality key names for MongoDB commands.
*/
enum LowCardinalityCommandKeyNames implements KeyName {
/**
* MongoDB collection name.
*/
MONGODB_COLLECTION {
@Override
public String getKeyName() {
return "spring.data.mongodb.collection";
}
},
/**
* MongoDB cluster identifier.
*/
MONGODB_CLUSTER_ID {
@Override
public String getKeyName() {
return "spring.data.mongodb.cluster_id";
}
}
}
/**
* Enums related to high cardinality key names for MongoDB commands.
*/
enum HighCardinalityCommandKeyNames implements KeyName {
/**
* MongoDB command value.
*/
MONGODB_COMMAND {
@Override
public String getKeyName() {
return "spring.data.mongodb.command";
}
}
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2013-2022 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.observability;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.mongodb.RequestContext;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandListener;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
/**
* Implement MongoDB's {@link CommandListener} using Micrometer's {@link Observation} API.
*
* @see https://github.com/openzipkin/brave/blob/release-5.13.0/instrumentation/mongodb/src/main/java/brave/mongodb/TraceMongoCommandListener.java
* @author OpenZipkin Brave Authors
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
public final class MongoObservationCommandListener
implements CommandListener, Observation.KeyValuesProviderAware<MongoHandlerKeyValuesProvider> {
private static final Log log = LogFactory.getLog(MongoObservationCommandListener.class);
private final ObservationRegistry observationRegistry;
private MongoHandlerKeyValuesProvider keyValuesProvider;
public MongoObservationCommandListener(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
this.keyValuesProvider = new DefaultMongoHandlerKeyValuesProvider();
}
@Override
public void commandStarted(CommandStartedEvent event) {
if (log.isDebugEnabled()) {
log.debug("Instrumenting the command started event");
}
String databaseName = event.getDatabaseName();
if ("admin".equals(databaseName)) {
return; // don't instrument commands like "endSessions"
}
RequestContext requestContext = event.getRequestContext();
if (requestContext == null) {
return;
}
Observation parent = observationFromContext(requestContext);
if (log.isDebugEnabled()) {
log.debug("Found the following observation passed from the mongo context [" + parent + "]");
}
if (parent == null) {
return;
}
setupObservability(event, requestContext);
}
@Override
public void commandSucceeded(CommandSucceededEvent event) {
if (event.getRequestContext() == null) {
return;
}
Observation observation = event.getRequestContext().getOrDefault(Observation.class, null);
if (observation == null) {
return;
}
MongoHandlerContext context = event.getRequestContext().get(MongoHandlerContext.class);
context.setCommandSucceededEvent(event);
if (log.isDebugEnabled()) {
log.debug("Command succeeded - will stop observation [" + observation + "]");
}
observation.stop();
}
@Override
public void commandFailed(CommandFailedEvent event) {
if (event.getRequestContext() == null) {
return;
}
Observation observation = event.getRequestContext().getOrDefault(Observation.class, null);
if (observation == null) {
return;
}
MongoHandlerContext context = event.getRequestContext().get(MongoHandlerContext.class);
context.setCommandFailedEvent(event);
if (log.isDebugEnabled()) {
log.debug("Command failed - will stop observation [" + observation + "]");
}
observation.error(event.getThrowable());
observation.stop();
}
/**
* Extract the {@link Observation} from MongoDB's {@link RequestContext}.
*
* @param context
* @return
*/
private static Observation observationFromContext(RequestContext context) {
Observation observation = context.getOrDefault(Observation.class, null);
if (observation != null) {
if (log.isDebugEnabled()) {
log.debug("Found a observation in mongo context [" + observation + "]");
}
return observation;
}
if (log.isDebugEnabled()) {
log.debug("No observation was found - will not create any child spans");
}
return null;
}
private void setupObservability(CommandStartedEvent event, RequestContext requestContext) {
MongoHandlerContext observationContext = new MongoHandlerContext(event, requestContext);
Observation observation = MongoObservation.MONGODB_COMMAND_OBSERVATION
.observation(this.observationRegistry, observationContext) //
.contextualName(observationContext.getContextualName()) //
.keyValuesProvider(this.keyValuesProvider) //
.start();
requestContext.put(Observation.class, observation);
requestContext.put(MongoHandlerContext.class, observationContext);
if (log.isDebugEnabled()) {
log.debug(
"Created a child observation [" + observation + "] for mongo instrumentation and put it in mongo context");
}
}
@Override
public void setKeyValuesProvider(MongoHandlerKeyValuesProvider mongoHandlerKeyValuesProvider) {
this.keyValuesProvider = mongoHandlerKeyValuesProvider;
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2013-2022 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.observability;
import io.micrometer.observation.Observation;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.TracingObservationHandler;
import java.net.InetSocketAddress;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.mongodb.MongoSocketException;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.event.CommandStartedEvent;
/**
* A {@link TracingObservationHandler} that handles {@link MongoHandlerContext}. It configures a span specific to Mongo
* operations.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
public class MongoTracingObservationHandler implements TracingObservationHandler<MongoHandlerContext> {
private static final Log log = LogFactory.getLog(MongoTracingObservationHandler.class);
private final Tracer tracer;
private boolean setRemoteIpAndPortEnabled;
public MongoTracingObservationHandler(Tracer tracer) {
this.tracer = tracer;
}
@Override
public Tracer getTracer() {
return this.tracer;
}
@Override
public void onStart(MongoHandlerContext context) {
CommandStartedEvent event = context.getCommandStartedEvent();
Span.Builder builder = this.tracer.spanBuilder() //
.name(context.getContextualName()) //
.kind(Span.Kind.CLIENT) //
.remoteServiceName("mongodb-" + event.getDatabaseName());
if (this.setRemoteIpAndPortEnabled) {
ConnectionDescription connectionDescription = event.getConnectionDescription();
if (connectionDescription != null) {
try {
InetSocketAddress socketAddress = connectionDescription.getServerAddress().getSocketAddress();
builder.remoteIpAndPort(socketAddress.getAddress().getHostAddress(), socketAddress.getPort());
} catch (MongoSocketException e) {
if (log.isDebugEnabled()) {
log.debug("Ignored exception when setting remote ip and port", e);
}
}
}
}
getTracingContext(context).setSpan(builder.start());
}
@Override
public void onStop(MongoHandlerContext context) {
Span span = getRequiredSpan(context);
tagSpan(context, span);
context.getRequestContext().delete(Observation.class);
context.getRequestContext().delete(MongoHandlerContext.class);
span.end();
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof MongoHandlerContext;
}
/**
* Should remote ip and port be set on the span.
*
* @return {@code true} when the remote ip and port should be set
*/
public boolean isSetRemoteIpAndPortEnabled() {
return this.setRemoteIpAndPortEnabled;
}
public void setSetRemoteIpAndPortEnabled(boolean setRemoteIpAndPortEnabled) {
this.setRemoteIpAndPortEnabled = setRemoteIpAndPortEnabled;
}
}

View File

@@ -61,7 +61,7 @@ class MongoCustomConversionsUnitTests {
registry -> registry.registerConverter(Foo.class, "name", mock(PropertyValueConverter.class)));
});
assertThat(conversions.hasPropertyValueConverter(persistentProperty)).isTrue();
assertThat(conversions.getPropertyValueConversions().hasValueConverter(persistentProperty)).isTrue();
}
static class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2002-2022 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.observability;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.TimerObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.test.simple.SimpleTracer;
import io.micrometer.tracing.test.simple.SpanAssert;
import io.micrometer.tracing.test.simple.TracerAssert;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames;
import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames;
import com.mongodb.ServerAddress;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerId;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
/**
* Series of test cases exercising {@link MongoObservationCommandListener} to ensure proper creation of {@link Span}s.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
class MongoObservationCommandListenerForTracingTests {
SimpleTracer simpleTracer;
MongoTracingObservationHandler handler;
MeterRegistry meterRegistry;
ObservationRegistry observationRegistry;
MongoObservationCommandListener listener;
@BeforeEach
void setup() {
this.simpleTracer = new SimpleTracer();
this.handler = new MongoTracingObservationHandler(simpleTracer);
this.meterRegistry = new SimpleMeterRegistry();
this.observationRegistry = ObservationRegistry.create();
this.observationRegistry.observationConfig().observationHandler(new TimerObservationHandler(meterRegistry));
this.observationRegistry.observationConfig().observationHandler(handler);
this.listener = new MongoObservationCommandListener(observationRegistry);
}
@Test
void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContext() {
// given
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
// when
commandStartedAndSucceeded(testRequestContext);
// then
assertThatMongoSpanIsClientWithTags().hasIpThatIsBlank().hasPortThatIsNotSet();
}
@Test
void successfullyCompletedCommandShouldCreateSpanWithAddressInfoWhenParentSampleInRequestContextAndHandlerAddressInfoEnabled() {
// given
handler.setSetRemoteIpAndPortEnabled(true);
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
// when
commandStartedAndSucceeded(testRequestContext);
// then
assertThatMongoSpanIsClientWithTags().hasIpThatIsNotBlank().hasPortThatIsSet();
}
@Test
void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() {
// given
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))), //
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandFailed( //
new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
// then
assertThatMongoSpanIsClientWithTags().assertThatThrowable().isInstanceOf(IllegalAccessException.class);
}
/**
* Create a parent {@link Observation} then wrap it inside a {@link TestRequestContext}.
*/
@NotNull
private TestRequestContext createTestRequestContextWithParentObservationAndStartIt() {
Observation parent = Observation.start("name", observationRegistry);
return TestRequestContext.withObservation(parent);
}
/**
* Execute MongoDB's {@link com.mongodb.event.CommandListener#commandStarted(CommandStartedEvent)} and
* {@link com.mongodb.event.CommandListener#commandSucceeded(CommandSucceededEvent)} operations against the
* {@link TestRequestContext} in order to inject some test data.
*
* @param testRequestContext
*/
private void commandStartedAndSucceeded(TestRequestContext testRequestContext) {
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))), //
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0));
}
/**
* Create a base MongoDB-based {@link SpanAssert} using Micrometer Tracing's fluent API. Other test methods can apply
* additional assertions.
*
* @return
*/
private SpanAssert assertThatMongoSpanIsClientWithTags() {
return TracerAssert.assertThat(simpleTracer).onlySpan() //
.hasNameEqualTo("insert user") //
.hasKindEqualTo(Span.Kind.CLIENT) //
.hasRemoteServiceNameEqualTo("mongodb-database") //
.hasTag(HighCardinalityCommandKeyNames.MONGODB_COMMAND.getKeyName(), "insert") //
.hasTag(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.getKeyName(), "user") //
.hasTagWithKey(LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID.getKeyName());
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2002-2022 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.observability;
import static io.micrometer.core.tck.MeterRegistryAssert.*;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.TimerObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames;
import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames;
import com.mongodb.ServerAddress;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerId;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
/**
* Series of test cases exercising {@link MongoObservationCommandListener}.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
class MongoObservationCommandListenerTests {
ObservationRegistry observationRegistry;
MeterRegistry meterRegistry;
MongoObservationCommandListener listener;
@BeforeEach
void setup() {
this.meterRegistry = new SimpleMeterRegistry();
this.observationRegistry = ObservationRegistry.create();
this.observationRegistry.observationConfig().observationHandler(new TimerObservationHandler(meterRegistry));
this.listener = new MongoObservationCommandListener(observationRegistry);
}
@Test
void commandStartedShouldNotInstrumentWhenAdminDatabase() {
// when
listener.commandStarted(new CommandStartedEvent(null, 0, null, "admin", "", null));
// then
assertThat(meterRegistry).hasNoMetrics();
}
@Test
void commandStartedShouldNotInstrumentWhenNoRequestContext() {
// when
listener.commandStarted(new CommandStartedEvent(null, 0, null, "some name", "", null));
// then
assertThat(meterRegistry).hasNoMetrics();
}
@Test
void commandStartedShouldNotInstrumentWhenNoParentSampleInRequestContext() {
// when
listener.commandStarted(new CommandStartedEvent(new TestRequestContext(), 0, null, "some name", "", null));
// then
assertThat(meterRegistry).hasNoMetrics();
}
@Test
void successfullyCompletedCommandShouldCreateTimerWhenParentSampleInRequestContext() {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))),
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0));
// then
assertThatTimerRegisteredWithTags();
}
@Test
void successfullyCompletedCommandWithCollectionHavingCommandNameShouldCreateTimerWhenParentSampleInRequestContext() {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))), //
"database", "aggregate", //
new BsonDocument("aggregate", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "aggregate", null, 0));
// then
assertThatTimerRegisteredWithTags();
}
@Test
void successfullyCompletedCommandWithoutClusterInformationShouldCreateTimerWhenParentSampleInRequestContext() {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, null, "database", "insert",
new BsonDocument("collection", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0));
// then
assertThat(meterRegistry).hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.getKeyName(),
KeyValues.of(KeyValue.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.getKeyName(), "user")));
}
@Test
void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))), //
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandFailed( //
new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
// then
assertThatTimerRegisteredWithTags();
}
private void assertThatTimerRegisteredWithTags() {
assertThat(meterRegistry) //
.hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.getKeyName(),
KeyValues.of(KeyValue.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.getKeyName(), "user"))) //
.hasTimerWithNameAndTagKeys(HighCardinalityCommandKeyNames.MONGODB_COMMAND.getKeyName(),
LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID.getKeyName());
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2013-2022 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.observability;
import io.micrometer.observation.Observation;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import com.mongodb.RequestContext;
/**
* A {@link Map}-based {@link RequestContext}. (For test purposes only).
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
class TestRequestContext implements RequestContext {
private final Map<Object, Object> map = new HashMap<>();
@Override
public <T> T get(Object key) {
return (T) map.get(key);
}
@Override
public boolean hasKey(Object key) {
return map.containsKey(key);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public void put(Object key, Object value) {
map.put(key, value);
}
@Override
public void delete(Object key) {
map.remove(key);
}
@Override
public int size() {
return map.size();
}
@Override
public Stream<Map.Entry<Object, Object>> stream() {
return map.entrySet().stream();
}
static TestRequestContext withObservation(Observation value) {
TestRequestContext testRequestContext = new TestRequestContext();
testRequestContext.put(Observation.class, value);
return testRequestContext;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2013-2022 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.observability;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.TimerObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.test.SampleTestRunner;
import io.micrometer.tracing.test.reporter.BuildingBlocks;
import java.io.IOException;
import java.util.Deque;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.PersonRepository;
import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.RequestContext;
import com.mongodb.WriteConcern;
import com.mongodb.client.MongoClients;
import com.mongodb.client.SynchronousContextProvider;
/**
* Collection of tests that log metrics and tracing with an external tracing tool. Since this external tool must be up
* and running after the test is completed, this test is ONLY run manually. Needed:
* {@code docker run -p 9411:9411 openzipkin/zipkin} and {@code docker run -p 27017:27017 mongo:latest} (either from
* Docker Desktop or within separate shells).
*
* @author Greg Turnquist
* @since 4.0.0
*/
@Disabled("Run this manually to visually test spans in Zipkin")
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class ZipkinIntegrationTests extends SampleTestRunner {
private static final MeterRegistry METER_REGISTRY = new SimpleMeterRegistry();
private static final ObservationRegistry OBSERVATION_REGISTRY = ObservationRegistry.create();
static {
OBSERVATION_REGISTRY.observationConfig().observationHandler(new TimerObservationHandler(METER_REGISTRY));
}
@Autowired PersonRepository repository;
ZipkinIntegrationTests() {
super(SampleRunnerConfig.builder().build(), OBSERVATION_REGISTRY, METER_REGISTRY);
}
@Override
public BiConsumer<BuildingBlocks, Deque<ObservationHandler>> customizeObservationHandlers() {
return (buildingBlocks, observationHandlers) -> {
observationHandlers.addLast(new MongoTracingObservationHandler(buildingBlocks.getTracer()));
};
}
@Override
public TracingSetup[] getTracingSetup() {
return new TracingSetup[] { TracingSetup.ZIPKIN_BRAVE };
}
@Override
public SampleTestRunnerConsumer yourCode() {
return (tracer, meterRegistry) -> {
repository.deleteAll();
repository.save(new Person("Dave", "Matthews", 42));
List<Person> people = repository.findByLastname("Matthews");
assertThat(people).hasSize(1);
assertThat(people.get(0)).extracting("firstname", "lastname").containsExactly("Dave", "Matthews");
repository.deleteAll();
System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString());
};
}
@Configuration
@EnableMongoRepositories
static class TestConfig {
@Bean
MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) {
return new MongoObservationCommandListener(registry);
}
@Bean
MongoDatabaseFactory mongoDatabaseFactory(MongoObservationCommandListener commandListener,
ObservationRegistry registry) {
ConnectionString connectionString = new ConnectionString(
String.format("mongodb://%s:%s/?w=majority&uuidrepresentation=javaLegacy", "127.0.0.1", 27017));
RequestContext requestContext = TestRequestContext.withObservation(Observation.start("name", registry));
SynchronousContextProvider contextProvider = () -> requestContext;
MongoClientSettings settings = MongoClientSettings.builder() //
.addCommandListener(commandListener) //
.contextProvider(contextProvider) //
.applyConnectionString(connectionString) //
.build();
return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable");
}
@Bean
MappingMongoConverter mongoConverter(MongoDatabaseFactory factory) {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.afterPropertiesSet();
return new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
}
@Bean
MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) {
MongoTemplate template = new MongoTemplate(mongoDatabaseFactory, mongoConverter);
template.setWriteConcern(WriteConcern.JOURNALED);
return template;
}
@Bean
public PropertiesFactoryBean namedQueriesProperties() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("META-INF/mongo-named-queries.properties"));
return bean;
}
@Bean
MongoRepositoryFactoryBean<PersonRepository, Person, String> repositoryFactoryBean(MongoOperations operations,
PropertiesFactoryBean namedQueriesProperties) throws IOException {
MongoRepositoryFactoryBean<PersonRepository, Person, String> factoryBean = new MongoRepositoryFactoryBean<>(
PersonRepository.class);
factoryBean.setMongoOperations(operations);
factoryBean.setNamedQueries(new PropertiesBasedNamedQueries(namedQueriesProperties.getObject()));
factoryBean.setCreateIndexesForQueryMethods(true);
return factoryBean;
}
@Bean
SampleEvaluationContextExtension contextExtension() {
return new SampleEvaluationContextExtension();
}
@Bean
ObservationRegistry registry() {
return OBSERVATION_REGISTRY;
}
}
}

View File

@@ -42,3 +42,4 @@ include::{spring-data-commons-docs}/repository-namespace-reference.adoc[leveloff
include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[leveloffset=+1]
include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[leveloffset=+1]
include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[leveloffset=+1]
include::reference/observability.adoc[leveloffset=+1]

View File

@@ -115,4 +115,4 @@ Professional Support :: Professional, from-the-source support, with guaranteed r
[[get-started:up-to-date]]
== Following Development
For information on the Spring Data Mongo source code repository, nightly builds, and snapshot artifacts, see the Spring Data Mongo https://projects.spring.io/spring-data-mongodb/[homepage]. You can help make Spring Data best serve the needs of the Spring community by interacting with developers through the Community on https://stackoverflow.com/questions/tagged/spring-data[Stack Overflow]. To follow developer activity, look for the mailing list information on the Spring Data Mongo https://projects.spring.io/spring-data-mongodb/[homepage]. If you encounter a bug or want to suggest an improvement, please create a ticket on the Spring Data https://github.com/spring-projects/spring-data-mongodb/issues[issue tracker]. To stay up to date with the latest news and announcements in the Spring eco system, subscribe to the Spring Community https://spring.io[Portal]. You can also follow the Spring https://spring.io/blog[blog] or the project team on Twitter (https://twitter.com/SpringData[SpringData]).
For information on the Spring Data Mongo source code repository, nightly builds, and snapshot artifacts, see the Spring Data Mongo https://spring.io/projects/spring-data-mongodb/[homepage]. You can help make Spring Data best serve the needs of the Spring community by interacting with developers through the Community on https://stackoverflow.com/questions/tagged/spring-data[Stack Overflow]. To follow developer activity, look for the mailing list information on the Spring Data Mongo https://spring.io/projects/spring-data-mongodb/[homepage]. If you encounter a bug or want to suggest an improvement, please create a ticket on the Spring Data https://github.com/spring-projects/spring-data-mongodb/issues[issue tracker]. To stay up to date with the latest news and announcements in the Spring eco system, subscribe to the Spring Community https://spring.io[Portal]. You can also follow the Spring https://spring.io/blog[blog] or the project team on Twitter (https://twitter.com/SpringData[SpringData]).

View File

@@ -162,7 +162,7 @@ calling `get()` before the actual conversion
| `URL`
| converter
| `{"website" : "https://projects.spring.io/spring-data-mongodb/" }`
| `{"website" : "https://spring.io/projects/spring-data-mongodb/" }`
| `Locale`
| converter

View File

@@ -1,11 +1,11 @@
[[mongo.property-converters]]
== Property Converters - Mapping specific fields
While <<mongo.custom-converters, type-based conversion>> already offers ways to influence the conversion and representation of certain types within the target store it has its limitations when only certain values or properties of a particular type should be considered for conversion.
Property-based converters allow configuring conversion rules on a per-property basis, either declarative, via `@ValueConverter`, or programmatic by registering a `PropertyValueConverter` for a specific property.
While <<mongo.custom-converters, type-based conversion>> already offers ways to influence the conversion and representation of certain types within the target store, it has limitations when only certain values or properties of a particular type should be considered for conversion.
Property-based converters allow configuring conversion rules on a per-property basis, either declaratively (via `@ValueConverter`) or programmatically (by registering a `PropertyValueConverter` for a specific property).
A `PropertyValueConverter` can transform a given value into its store representation (**write**) and back (**read**) as shown in the snippet below.
The additional `ValueConversionContext` provides additional information, such as mapping metadata and direct `read`/`write` methods.
A `PropertyValueConverter` can transform a given value into its store representation (write) and back (read) as the following listing shows.
The additional `ValueConversionContext` provides additional information, such as mapping metadata and direct `read` and `write` methods.
.A simple PropertyValueConverter
====
@@ -26,18 +26,18 @@ class ReversingValueConverter implements PropertyValueConverter<String, String,
----
====
`PropertyValueConverter` instances can be obtained via `CustomConversions#getPropertyValueConverter(…)` delegating to `PropertyValueConversions`, typically using a `PropertyValueConverterFactory` providing the actual converter.
Depending on the applications needs, multiple instances of `PropertyValueConverterFactory` can be chained or decorated, for example to apply caching.
By default, a caching implementation is used that is capable of serving types with a default constructor or enum values.
A set of predefined factories is available through `PropertyValueConverterFactory` factory methods.
Use `PropertyValueConverterFactory.beanFactoryAware(…)` to obtain a `PropertyValueConverter` instances from an `ApplicationContext`.
You can obtain `PropertyValueConverter` instances from `CustomConversions#getPropertyValueConverter(…)` by delegating to `PropertyValueConversions`, typically by using a `PropertyValueConverterFactory` to provide the actual converter.
Depending on your application's needs, you can chain or decorate multiple instances of `PropertyValueConverterFactory` -- for example, to apply caching.
By default, Spring Data MongoDB uses a caching implementation that can serve types with a default constructor or enum values.
A set of predefined factories is available through the factory methods in `PropertyValueConverterFactory`.
You can use `PropertyValueConverterFactory.beanFactoryAware(…)` to obtain a `PropertyValueConverter` instance from an `ApplicationContext`.
You can change the default behavior through `ConverterConfiguration`.
[[mongo.property-converters.declarative]]
=== Declarative Value Converter
The most straight forward usage of a `PropertyValueConverter` is by annotating properties with the `@ValueConverter` annotation that defines the converter type.
The most straight forward usage of a `PropertyValueConverter` is by annotating properties with the `@ValueConverter` annotation that defines the converter type:
.Declarative PropertyValueConverter
====
@@ -54,8 +54,8 @@ class Person {
[[mongo.property-converters.programmatic]]
=== Programmatic Value Converter Registration
Programmatic registration registers `PropertyValueConverter` instances for properties within an entity model using a `PropertyValueConverterRegistrar` as shown below.
The difference to declarative registration is that programmatic registration happens entirely outside of the entity model.
Programmatic registration registers `PropertyValueConverter` instances for properties within an entity model by using a `PropertyValueConverterRegistrar`, as the following example shows.
The difference between declarative registration and programmatic registration is that programmatic registration happens entirely outside of the entity model.
Such an approach is useful if you cannot or do not want to annotate the entity model.
.Programmatic PropertyValueConverter registration
@@ -76,25 +76,22 @@ registrar.registerConverter(Person.class, Person::getSsn())
<2> Type safe variant that allows to register a converter and its conversion functions.
====
[WARNING]
====
Dot-notation (such as `registerConverter(Person.class, "address.street", …)`) nagivating across properties into subdocuments is *not* supported when registering converters.
====
WARNING: Dot notation (such as `registerConverter(Person.class, "address.street", …)`) for nagivating across properties into subdocuments is *not* supported when registering converters.
[[mongo.property-converters.value-conversions]]
=== MongoDB property value conversions
The above sections outlined the purpose an overall structure of `PropertyValueConverters`.
This section will focus on MongoDB specific aspects.
The preceding sections outlined the purpose an overall structure of `PropertyValueConverters`.
This section focuses on MongoDB specific aspects.
==== MongoValueConverter and MongoConversionContext
`MongoValueConverter` offers a pre typed `PropertyValueConverter` interface leveraging the `MongoConversionContext`.
`MongoValueConverter` offers a pre-typed `PropertyValueConverter` interface that uses `MongoConversionContext`.
==== MongoCustomConversions configuration
`MongoCustomConversions` are by default capable of handling declarative value converters depending on the configured `PropertyValueConverterFactory`.
`MongoConverterConfigurationAdapter` is there to help set up programmatic value conversions or define the `PropertyValueConverterFactory` to be used.
By default, `MongoCustomConversions` can handle declarative value converters, depending on the configured `PropertyValueConverterFactory`.
`MongoConverterConfigurationAdapter` helps to set up programmatic value conversions or define the `PropertyValueConverterFactory` to be used.
.Configuration Sample
====

View File

@@ -2,8 +2,6 @@
= MongoDB Repositories
[[mongo-repo-intro]]
== Introduction
This chapter points out the specialties for repository support for MongoDB. This chapter builds on the core repository support explained in <<repositories>>. You should have a sound understanding of the basic concepts explained there.
[[mongo-repo-usage]]
@@ -292,13 +290,13 @@ NOTE: If the property criterion compares a document, the order of the fields and
[[mongodb.repositories.queries.update]]
=== Repository Update Methods
The keywords in the preceding table can also be used to create queries that identify matching documents for running updates on them.
The actual update action is defined via the `@Update` annotation on the method itself as shown in the snippet below. +
Please note that the naming schema for derived queries starts with `find`.
Using _update_ (as in `updateAllByLastname(...)`) is only allowed in combination with `@Query`.
You can also use the keywords in the preceding table to create queries that identify matching documents for running updates on them.
The actual update action is defined by the `@Update` annotation on the method itself, as the following listing shows.
Note that the naming schema for derived queries starts with `find`.
Using `update` (as in `updateAllByLastname(...)`) is allowed only in combination with `@Query`.
The update is applied to *all* matching documents and it is *not* possible to limit the scope by passing in a `Page` nor using any of the <<repositories.limit-query-result,limiting keywords>>. +
The return type can be either `void` or a _numeric_ type, such as `long` which holds the number of modified documents.
The update is applied to *all* matching documents and it is *not* possible to limit the scope by passing in a `Page` or by using any of the <<repositories.limit-query-result,limiting keywords>>.
The return type can be either `void` or a _numeric_ type, such as `long`, to hold the number of modified documents.
.Update Methods
====
@@ -326,18 +324,15 @@ public interface PersonRepository extends CrudRepository<Person, String> {
void updateAllByLastname(String lastname, int increment); <6>
}
----
<1> The filter query for the update is derived from the method name. The update is as is and does not bind any parameters.
<2> The actual increment value is defined by the _increment_ method argument that is bound to the `?1` placeholder.
<3> It is possible to use SpEL for parameter binding.
<1> The filter query for the update is derived from the method name. The update is "`as is`" and does not bind any parameters.
<2> The actual increment value is defined by the `increment` method argument that is bound to the `?1` placeholder.
<3> Use the Spring Expression Language (SpEL) for parameter binding.
<4> Use the `pipeline` attribute to issue <<mongo-template.aggregation-update,aggregation pipeline updates>>.
<5> The update may contain complex objects.
<6> Combine a <<mongodb.repositories.queries.json-based,string based query>> with an update.
====
[WARNING]
====
Repository updates do not emit persistence nor mapping lifecycle events.
====
WARNING: Repository updates do not emit persistence nor mapping lifecycle events.
[[mongodb.repositories.queries.delete]]
=== Repository Delete Queries

View File

@@ -0,0 +1,8 @@
:root-target: ../../../../target/
[[observability]]
== Observability metadata
include::{root-target}_metrics.adoc[]
include::{root-target}_spans.adoc[]

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 4.0 M2 (2022.0.0)
Spring Data MongoDB 4.0 M4 (2022.0.0)
Copyright (c) [2010-2019] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -32,6 +32,8 @@ conditions of the subcomponent's license, as noted in the LICENSE file.