diff --git a/driver-core/src/main/com/mongodb/MongoDriverInformation.java b/driver-core/src/main/com/mongodb/MongoDriverInformation.java index 242f27f65b2..a3b28b62fad 100644 --- a/driver-core/src/main/com/mongodb/MongoDriverInformation.java +++ b/driver-core/src/main/com/mongodb/MongoDriverInformation.java @@ -16,13 +16,15 @@ package com.mongodb; +import com.mongodb.annotations.Internal; import com.mongodb.annotations.NotThreadSafe; +import com.mongodb.internal.client.DriverInformation; +import com.mongodb.internal.client.DriverInformationHelper; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; /** @@ -46,9 +48,7 @@ * @mongodb.server.release 3.4 */ public final class MongoDriverInformation { - private final List driverNames; - private final List driverVersions; - private final List driverPlatforms; + private final List driverInformationList; /** * Convenience method to create a Builder. @@ -75,7 +75,7 @@ public static Builder builder(final MongoDriverInformation mongoDriverInformatio * @return the driverNames */ public List getDriverNames() { - return driverNames; + return DriverInformationHelper.getNames(driverInformationList); } /** @@ -84,7 +84,7 @@ public List getDriverNames() { * @return the driverVersions */ public List getDriverVersions() { - return driverVersions; + return DriverInformationHelper.getVersions(driverInformationList); } /** @@ -93,7 +93,15 @@ public List getDriverVersions() { * @return the driverPlatforms */ public List getDriverPlatforms() { - return driverPlatforms; + return DriverInformationHelper.getPlatforms(driverInformationList); + } + + /** + * For internal use only + */ + @Internal + public List getDriverInformationList() { + return driverInformationList; } /** @@ -101,7 +109,7 @@ public List getDriverPlatforms() { */ @NotThreadSafe public static final class Builder { - private final MongoDriverInformation driverInformation; + private final MongoDriverInformation mongoDriverInformation; private String driverName; private String driverVersion; private String driverPlatform; @@ -147,38 +155,26 @@ public Builder driverPlatform(final String driverPlatform) { * @return the driver information */ public MongoDriverInformation build() { - isTrue("You must also set the driver name when setting the driver version", !(driverName == null && driverVersion != null)); - - List names = prependToList(driverInformation.getDriverNames(), driverName); - List versions = prependToList(driverInformation.getDriverVersions(), driverVersion); - List platforms = prependToList(driverInformation.getDriverPlatforms(), driverPlatform); - return new MongoDriverInformation(names, versions, platforms); - } - - private List prependToList(final List stringList, final String value) { - if (value == null) { - return stringList; - } else { - ArrayList newList = new ArrayList<>(); - newList.add(value); - newList.addAll(stringList); - return Collections.unmodifiableList(newList); + DriverInformation driverInformation = new DriverInformation(driverName, driverVersion, driverPlatform); + if (mongoDriverInformation.driverInformationList.contains(driverInformation)) { + return mongoDriverInformation; } + + List driverInformationList = new ArrayList<>(mongoDriverInformation.driverInformationList); + driverInformationList.add(driverInformation); + return new MongoDriverInformation(Collections.unmodifiableList(driverInformationList)); } private Builder() { - List immutableEmptyList = Collections.emptyList(); - driverInformation = new MongoDriverInformation(immutableEmptyList, immutableEmptyList, immutableEmptyList); + mongoDriverInformation = new MongoDriverInformation(Collections.emptyList()); } private Builder(final MongoDriverInformation driverInformation) { - this.driverInformation = notNull("driverInformation", driverInformation); + this.mongoDriverInformation = notNull("driverInformation", driverInformation); } } - private MongoDriverInformation(final List driverNames, final List driverVersions, final List driverPlatforms) { - this.driverNames = driverNames; - this.driverVersions = driverVersions; - this.driverPlatforms = driverPlatforms; + private MongoDriverInformation(final List driverInformation) { + this.driverInformationList = notNull("driverInformation", driverInformation); } } diff --git a/driver-core/src/main/com/mongodb/annotations/Internal.java b/driver-core/src/main/com/mongodb/annotations/Internal.java new file mode 100644 index 00000000000..914246b7c7e --- /dev/null +++ b/driver-core/src/main/com/mongodb/annotations/Internal.java @@ -0,0 +1,42 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2010 The Guava Authors + * Copyright 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Signifies that a public API element is intended for internal use only. + * + *

It is inadvisable for applications to use Internal APIs as they are intended for internal library purposes only.

+ */ +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE }) +@Documented +@Alpha(Reason.CLIENT) +public @interface Internal { +} diff --git a/driver-core/src/main/com/mongodb/internal/client/DriverInformation.java b/driver-core/src/main/com/mongodb/internal/client/DriverInformation.java new file mode 100644 index 00000000000..2eed2ff682e --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/client/DriverInformation.java @@ -0,0 +1,81 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.client; + +import com.mongodb.lang.Nullable; + +import java.util.Objects; + +public final class DriverInformation { + @Nullable + private final String driverName; + @Nullable + private final String driverVersion; + @Nullable + private final String driverPlatform; + + public DriverInformation(@Nullable final String driverName, + @Nullable final String driverVersion, + @Nullable final String driverPlatform) { + this.driverName = driverName == null || driverName.isEmpty() ? null : driverName; + this.driverVersion = driverVersion == null || driverVersion.isEmpty() ? null : driverVersion; + this.driverPlatform = driverPlatform == null || driverPlatform.isEmpty() ? null : driverPlatform; + } + + @Nullable + public String getDriverName() { + return driverName; + } + + @Nullable + public String getDriverVersion() { + return driverVersion; + } + + @Nullable + public String getDriverPlatform() { + return driverPlatform; + } + + @Override + public boolean equals(final Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + + final DriverInformation that = (DriverInformation) o; + return Objects.equals(driverName, that.driverName) + && Objects.equals(driverVersion, that.driverVersion) + && Objects.equals(driverPlatform, that.driverPlatform); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(driverName); + result = 31 * result + Objects.hashCode(driverVersion); + result = 31 * result + Objects.hashCode(driverPlatform); + return result; + } + + @Override + public String toString() { + return "DriverInformation{" + + "driverName='" + driverName + '\'' + + ", driverVersion='" + driverVersion + '\'' + + ", driverPlatform='" + driverPlatform + '\'' + + '}'; + } +} diff --git a/driver-core/src/main/com/mongodb/internal/client/DriverInformationHelper.java b/driver-core/src/main/com/mongodb/internal/client/DriverInformationHelper.java new file mode 100644 index 00000000000..76149f1a83b --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/client/DriverInformationHelper.java @@ -0,0 +1,58 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.client; + +import com.mongodb.internal.build.MongoDriverVersion; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.lang.System.getProperty; + +public final class DriverInformationHelper { + + public static final DriverInformation INITIAL_DRIVER_INFORMATION = + new DriverInformation(MongoDriverVersion.NAME, MongoDriverVersion.VERSION, + format("Java/%s/%s", getProperty("java.vendor", "unknown-vendor"), + getProperty("java.runtime.version", "unknown-version"))); + + public static List getNames(final List driverInformation) { + return getDriverField(DriverInformation::getDriverName, driverInformation); + } + + public static List getVersions(final List driverInformation) { + return getDriverField(DriverInformation::getDriverVersion, driverInformation); + } + + public static List getPlatforms(final List driverInformation) { + return getDriverField(DriverInformation::getDriverPlatform, driverInformation); + } + + private static List getDriverField(final Function fieldSupplier, + final List driverInformation) { + return Collections.unmodifiableList(driverInformation.stream() + .map(fieldSupplier) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + } + + private DriverInformationHelper() { + } +} diff --git a/driver-core/src/main/com/mongodb/internal/client/package-info.java b/driver-core/src/main/com/mongodb/internal/client/package-info.java new file mode 100644 index 00000000000..03c3a2bfd9b --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/client/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains classes for internal client functionality. + */ + +@NonNullApi +package com.mongodb.internal.client; + +import com.mongodb.lang.NonNullApi; diff --git a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java index c83a32ce4d4..6c98a87a9bd 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java @@ -19,7 +19,8 @@ import com.mongodb.MongoDriverInformation; import com.mongodb.annotations.ThreadSafe; import com.mongodb.internal.VisibleForTesting; -import com.mongodb.internal.build.MongoDriverVersion; +import com.mongodb.internal.client.DriverInformation; +import com.mongodb.internal.client.DriverInformationHelper; import com.mongodb.lang.Nullable; import org.bson.BsonBinaryWriter; import org.bson.BsonDocument; @@ -40,8 +41,8 @@ import static com.mongodb.assertions.Assertions.isTrueArgument; import static com.mongodb.internal.Locks.withLock; +import static com.mongodb.internal.client.DriverInformationHelper.INITIAL_DRIVER_INFORMATION; import static com.mongodb.internal.connection.FaasEnvironment.getFaasEnvironment; -import static java.lang.String.format; import static java.lang.System.getProperty; import static java.nio.file.Paths.get; @@ -58,17 +59,16 @@ public class ClientMetadata { private static final int MAXIMUM_CLIENT_METADATA_ENCODED_SIZE = 512; private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final String applicationName; + private final List driverInformationList; private BsonDocument clientMetadataBsonDocument; - private DriverInformation driverInformation; public ClientMetadata(@Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation) { this.applicationName = applicationName; + this.driverInformationList = new ArrayList<>(); withLock(readWriteLock.writeLock(), () -> { - this.driverInformation = DriverInformation.from( - mongoDriverInformation.getDriverNames(), - mongoDriverInformation.getDriverVersions(), - mongoDriverInformation.getDriverPlatforms()); - this.clientMetadataBsonDocument = createClientMetadataDocument(applicationName, driverInformation); + driverInformationList.add(INITIAL_DRIVER_INFORMATION); + driverInformationList.addAll(mongoDriverInformation.getDriverInformationList()); + this.clientMetadataBsonDocument = createClientMetadataDocument(applicationName, driverInformationList); }); } @@ -81,35 +81,40 @@ public BsonDocument getBsonDocument() { public void append(final MongoDriverInformation mongoDriverInformationToAppend) { withLock(readWriteLock.writeLock(), () -> { - this.driverInformation.append( - mongoDriverInformationToAppend.getDriverNames(), - mongoDriverInformationToAppend.getDriverVersions(), - mongoDriverInformationToAppend.getDriverPlatforms()); - this.clientMetadataBsonDocument = createClientMetadataDocument(applicationName, driverInformation); + boolean hasAddedNewData = false; + for (DriverInformation driverInformation : mongoDriverInformationToAppend.getDriverInformationList()) { + if (!driverInformationList.contains(driverInformation)) { + hasAddedNewData = true; + driverInformationList.add(driverInformation); + } + } + if (hasAddedNewData) { + clientMetadataBsonDocument = createClientMetadataDocument(applicationName, driverInformationList); + } }); } private static BsonDocument createClientMetadataDocument(@Nullable final String applicationName, - final DriverInformation driverInformation) { + final List driverInformationList) { if (applicationName != null) { isTrueArgument("applicationName UTF-8 encoding length <= 128", applicationName.getBytes(StandardCharsets.UTF_8).length <= 128); } // client fields are added in "preservation" order: - BsonDocument client = new BsonDocument(); - tryWithLimit(client, d -> putAtPath(d, "application.name", applicationName)); + BsonDocument clientMetadata = new BsonDocument(); + tryWithLimit(clientMetadata, d -> putAtPath(d, "application.name", applicationName)); // required fields: - tryWithLimit(client, d -> { - putAtPath(d, "driver.name", driverInformation.getInitialDriverName()); - putAtPath(d, "driver.version", driverInformation.getInitialDriverVersion()); + tryWithLimit(clientMetadata, d -> { + putAtPath(d, "driver.name", INITIAL_DRIVER_INFORMATION.getDriverName()); + putAtPath(d, "driver.version", INITIAL_DRIVER_INFORMATION.getDriverVersion()); }); - tryWithLimit(client, d -> putAtPath(d, "os.type", getOperatingSystemType(getOperatingSystemName()))); + tryWithLimit(clientMetadata, d -> putAtPath(d, "os.type", getOperatingSystemType(getOperatingSystemName()))); // full driver information: - tryWithLimit(client, d -> { - putAtPath(d, "driver.name", listToString(driverInformation.getAllDriverNames())); - putAtPath(d, "driver.version", listToString(driverInformation.getAllDriverVersions())); + tryWithLimit(clientMetadata, d -> { + putAtPath(d, "driver.name", listToString(DriverInformationHelper.getNames(driverInformationList))); + putAtPath(d, "driver.version", listToString(DriverInformationHelper.getVersions(driverInformationList))); }); // optional fields: @@ -117,21 +122,20 @@ private static BsonDocument createClientMetadataDocument(@Nullable final String ClientMetadata.ContainerRuntime containerRuntime = ClientMetadata.ContainerRuntime.determineExecutionContainer(); ClientMetadata.Orchestrator orchestrator = ClientMetadata.Orchestrator.determineExecutionOrchestrator(); - tryWithLimit(client, d -> putAtPath(d, "platform", driverInformation.getInitialDriverPlatform())); - tryWithLimit(client, d -> putAtPath(d, "platform", listToString(driverInformation.getAllDriverPlatforms()))); - tryWithLimit(client, d -> putAtPath(d, "os.name", getOperatingSystemName())); - tryWithLimit(client, d -> putAtPath(d, "os.architecture", getProperty("os.arch", "unknown"))); - tryWithLimit(client, d -> putAtPath(d, "os.version", getProperty("os.version", "unknown"))); - - tryWithLimit(client, d -> putAtPath(d, "env.name", faasEnvironment.getName())); - tryWithLimit(client, d -> putAtPath(d, "env.timeout_sec", faasEnvironment.getTimeoutSec())); - tryWithLimit(client, d -> putAtPath(d, "env.memory_mb", faasEnvironment.getMemoryMb())); - tryWithLimit(client, d -> putAtPath(d, "env.region", faasEnvironment.getRegion())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "platform", INITIAL_DRIVER_INFORMATION.getDriverPlatform())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "platform", listToString(DriverInformationHelper.getPlatforms(driverInformationList)))); + tryWithLimit(clientMetadata, d -> putAtPath(d, "os.name", getOperatingSystemName())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "os.architecture", getProperty("os.arch", "unknown"))); + tryWithLimit(clientMetadata, d -> putAtPath(d, "os.version", getProperty("os.version", "unknown"))); - tryWithLimit(client, d -> putAtPath(d, "env.container.runtime", containerRuntime.getName())); - tryWithLimit(client, d -> putAtPath(d, "env.container.orchestrator", orchestrator.getName())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "env.name", faasEnvironment.getName())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "env.timeout_sec", faasEnvironment.getTimeoutSec())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "env.memory_mb", faasEnvironment.getMemoryMb())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "env.region", faasEnvironment.getRegion())); - return client; + tryWithLimit(clientMetadata, d -> putAtPath(d, "env.container.runtime", containerRuntime.getName())); + tryWithLimit(clientMetadata, d -> putAtPath(d, "env.container.orchestrator", orchestrator.getName())); + return clientMetadata; } private static void putAtPath(final BsonDocument d, final String path, @Nullable final String value) { @@ -306,67 +310,4 @@ private static boolean nameStartsWith(final String name, final String... prefixe return false; } - /** - * Holds driver information of client.driver field - * in {@link ClientMetadata#clientMetadataBsonDocument}. - */ - private static class DriverInformation { - private final List driverNames; - private final List driverVersions; - private final List driverPlatforms; - private final String initialPlatform; - - DriverInformation() { - this.driverNames = new ArrayList<>(); - driverNames.add(MongoDriverVersion.NAME); - - this.driverVersions = new ArrayList<>(); - driverVersions.add(MongoDriverVersion.VERSION); - - this.initialPlatform = format("Java/%s/%s", getProperty("java.vendor", "unknown-vendor"), - getProperty("java.runtime.version", "unknown-version")); - this.driverPlatforms = new ArrayList<>(); - driverPlatforms.add(initialPlatform); - } - - static DriverInformation from(final List driverNames, - final List driverVersions, - final List driverPlatforms) { - DriverInformation driverInformation = new DriverInformation(); - return driverInformation.append(driverNames, driverVersions, driverPlatforms); - } - - DriverInformation append(final List driverNames, - final List driverVersions, - final List driverPlatforms) { - this.driverNames.addAll(driverNames); - this.driverVersions.addAll(driverVersions); - this.driverPlatforms.addAll(driverPlatforms); - return this; - } - - public String getInitialDriverPlatform() { - return initialPlatform; - } - - public String getInitialDriverName() { - return MongoDriverVersion.NAME; - } - - public String getInitialDriverVersion() { - return MongoDriverVersion.VERSION; - } - - public List getAllDriverNames() { - return driverNames; - } - - public List getAllDriverVersions() { - return driverVersions; - } - - public List getAllDriverPlatforms() { - return driverPlatforms; - } - } } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java index bb2e5dc7351..6ea8c506ff4 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java @@ -353,20 +353,18 @@ void testUpdateClientMetadataDocument(@Nullable final String driverVersion, createExpectedClientMetadataDocument(null, initialDriverInformation), initialClientMetadataDocument); - MongoDriverInformation.Builder builder; - builder = MongoDriverInformation.builder(); + MongoDriverInformation.Builder expectedUpdatedMetadataBuilder = MongoDriverInformation.builder(initialDriverInformation); + ofNullable(driverName).ifPresent(expectedUpdatedMetadataBuilder::driverName); + ofNullable(driverVersion).ifPresent(expectedUpdatedMetadataBuilder::driverVersion); + ofNullable(driverPlatform).ifPresent(expectedUpdatedMetadataBuilder::driverPlatform); + MongoDriverInformation expectedUpdatedMetadata = expectedUpdatedMetadataBuilder.build(); + + MongoDriverInformation.Builder builder = MongoDriverInformation.builder(); ofNullable(driverName).ifPresent(builder::driverName); ofNullable(driverVersion).ifPresent(builder::driverVersion); ofNullable(driverPlatform).ifPresent(builder::driverPlatform); MongoDriverInformation metadataToAppend = builder.build(); - //We pass metadataToAppend to a builder and prepend with initial driver information. - MongoDriverInformation expectedUpdatedMetadata = MongoDriverInformation.builder(metadataToAppend) - .driverName("mongo-spark") - .driverVersion("2.0.0") - .driverPlatform("Scala 2.10 / Spark 2.0.0") - .build(); - //when clientMetadata.append(metadataToAppend); BsonDocument updatedClientMetadata = clientMetadata.getBsonDocument(); diff --git a/driver-core/src/test/unit/com/mongodb/MongoDriverInformationSpecification.groovy b/driver-core/src/test/unit/com/mongodb/MongoDriverInformationSpecification.groovy index cd2815bf773..58bfbd5c424 100644 --- a/driver-core/src/test/unit/com/mongodb/MongoDriverInformationSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/MongoDriverInformationSpecification.groovy @@ -30,7 +30,7 @@ class MongoDriverInformationSpecification extends Specification { options.getDriverPlatforms() == [] } - def 'should not prepend data if none has been added'() { + def 'should not append data if none has been added'() { given: def options = MongoDriverInformation.builder(MongoDriverInformation.builder().build()).build() @@ -40,50 +40,42 @@ class MongoDriverInformationSpecification extends Specification { options.getDriverPlatforms() == [] } - def 'should prepend data to the list'() { + def 'should append data to the list'() { given: - def scalaDriverInfo = MongoDriverInformation.builder() - .driverName('mongo-scala-driver') - .driverVersion('1.2.0') - .driverPlatform('Scala 2.11') - .build() - - def options = MongoDriverInformation.builder(scalaDriverInfo) + def javaDriverInfo = MongoDriverInformation.builder() .driverName('mongo-java-driver') .driverVersion('3.4.0') .driverPlatform('Java oracle64-1.8.0.31') .build() + def options = MongoDriverInformation.builder(javaDriverInfo) + .driverName('mongo-scala-driver') + .driverVersion('1.2.0') + .driverPlatform('Scala 2.11') + .build() + expect: options.getDriverNames() == ['mongo-java-driver', 'mongo-scala-driver'] options.getDriverVersions() == ['3.4.0', '1.2.0'] options.getDriverPlatforms() == ['Java oracle64-1.8.0.31', 'Scala 2.11'] } - def 'should only prepend data that has been set'() { + def 'should only append data that has been set'() { given: - def scalaDriverInfo = MongoDriverInformation.builder().driverName('mongo-scala-driver').build() - - def options = MongoDriverInformation.builder(scalaDriverInfo) + def javaDriverInfo = MongoDriverInformation.builder() .driverName('mongo-java-driver') .driverVersion('3.4.0') .driverPlatform('Java oracle64-1.8.0.31') .build() + def options = MongoDriverInformation.builder(javaDriverInfo).driverName('mongo-scala-driver').build() + expect: options.getDriverNames() == ['mongo-java-driver', 'mongo-scala-driver'] options.getDriverVersions() == ['3.4.0'] options.getDriverPlatforms() == ['Java oracle64-1.8.0.31'] } - def 'should error if trying to set a version without setting a name'() { - when: - MongoDriverInformation.builder().driverVersion('0.21.1-alpha').build() - - then: - thrown IllegalStateException - } - def 'should null check the passed MongoDriverInformation'() { when: MongoDriverInformation.builder(null).build() diff --git a/driver-scala/src/test/scala/org/mongodb/scala/MongoDriverInformationSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/MongoDriverInformationSpec.scala index f6e1f994073..a1ca4419ceb 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/MongoDriverInformationSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/MongoDriverInformationSpec.scala @@ -26,7 +26,7 @@ class MongoDriverInformationSpec extends BaseSpec { MongoDriverInformationClass.getDeclaredFields.filter(f => isStatic(f.getModifiers)).map(_.getName).toSet val wrappedMethods = MongoDriverInformationClass.getDeclaredMethods.filter(f => isStatic(f.getModifiers)).map(_.getName).toSet - val exclusions = Set("$VALUES", "$values", "valueOf", "values") + val exclusions = Set("$VALUES", "$values", "valueOf", "values", "access$200") val wrapped = (wrappedFields ++ wrappedMethods) -- exclusions val local = MongoDriverInformation.getClass.getDeclaredMethods.map(_.getName).toSet -- Set( diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java index 5bc0ff5936d..b958afcf145 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java @@ -19,6 +19,7 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoDriverInformation; import com.mongodb.event.CommandStartedEvent; +import com.mongodb.internal.client.DriverInformation; import com.mongodb.internal.connection.InternalStreamConnection; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.internal.connection.TestConnectionPoolListener; @@ -27,6 +28,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -46,7 +48,7 @@ import static org.junit.jupiter.api.Assumptions.assumeFalse; /** - * See spec + * See Prose tests */ public abstract class AbstractClientMetadataProseTest { @@ -71,53 +73,38 @@ public void tearDown() { InternalStreamConnection.setRecordEverything(false); } - public static Stream provideDriverInformation() { - return Stream.of( - Arguments.of("1.0", "Framework", "Framework Platform"), - Arguments.of("1.0", "Framework", null), - Arguments.of(null, "Framework", "Framework Platform"), - Arguments.of(null, "Framework", null) - ); - } - - @ParameterizedTest + @DisplayName("Test 1: Test that the driver updates metadata") + @ParameterizedTest(name = "{index} => {arguments}") @MethodSource("provideDriverInformation") - void shouldAppendToPreviousMetadataWhenUpdatedAfterInitialization(@Nullable final String driverVersion, - @Nullable final String driverName, - @Nullable final String driverPlatform) { + void testThatTheDriverUpdatesMetadata(final DriverInformation driverInformation) { //given - MongoDriverInformation initialWrappingLibraryDriverInformation = MongoDriverInformation.builder() - .driverName("library") - .driverVersion("1.2") - .driverPlatform("Library Platform") - .build(); - - try (MongoClient mongoClient = createMongoClient(initialWrappingLibraryDriverInformation, getMongoClientSettingsBuilder() - .applyToConnectionPoolSettings(builder -> - builder.maxConnectionIdleTime(1, TimeUnit.MILLISECONDS)) - .build())) { - - BsonDocument initialClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + try (MongoClient mongoClient = createMongoClient(getInitialMongoDriverInformation(), getMongoClientSettings())) { + sleep(5); // wait for connection to become idle + BsonDocument initialClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); - BsonDocument driverInformation = initialClientMetadata.getDocument("driver"); - String generatedDriverName = driverInformation.get("name").asString().getValue(); - String generatedVersionName = driverInformation.get("version").asString().getValue(); + + BsonDocument generatedDriverInformation = initialClientMetadata.getDocument("driver"); + String generatedDriverName = generatedDriverInformation.get("name").asString().getValue(); + String generatedVersionName = generatedDriverInformation.get("version").asString().getValue(); String generatedPlatformName = initialClientMetadata.get("platform").asString().getValue(); //when sleep(5); // wait for connection to become idle - updateClientMetadata(driverVersion, driverName, driverPlatform, mongoClient); + mongoClient.appendMetadata(getMongoDriverInformation(driverInformation)); //then BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); BsonDocument updatedDriverInformation = updatedClientMetadata.getDocument("driver"); + String driverName = driverInformation.getDriverName(); + String driverVersion = driverInformation.getDriverVersion(); + String driverPlatform = driverInformation.getDriverPlatform(); String expectedDriverName = driverName == null ? generatedDriverName : generatedDriverName + "|" + driverName; String expectedDriverVersion = driverVersion == null ? generatedVersionName : generatedVersionName + "|" + driverVersion; String expectedDriverPlatform = driverPlatform == null ? generatedPlatformName : generatedPlatformName + "|" + driverPlatform; - assertEquals(updatedDriverInformation.getString("name").getValue(), expectedDriverName); + assertEquals(expectedDriverName, updatedDriverInformation.getString("name").getValue()); assertTrue(updatedDriverInformation.getString("version").getValue().endsWith(expectedDriverVersion)); assertTrue(updatedClientMetadata.getString("platform").getValue().endsWith(expectedDriverPlatform)); @@ -127,16 +114,14 @@ void shouldAppendToPreviousMetadataWhenUpdatedAfterInitialization(@Nullable fina } } - @ParameterizedTest + @DisplayName("Test 2: Multiple Successive Metadata Updates") + @ParameterizedTest(name = "{index} => {arguments}") @MethodSource("provideDriverInformation") - void shouldAppendToDefaultClientMetadataWhenUpdatedAfterInitialization(@Nullable final String driverVersion, - @Nullable final String driverName, - @Nullable final String driverPlatform) { + void testMultipleSuccessiveMetadataUpdates(final DriverInformation driverInformation) { //given - try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettingsBuilder() - .applyToConnectionPoolSettings(builder -> - builder.maxConnectionIdleTime(1, TimeUnit.MILLISECONDS)) - .build())) { + try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettings())) { + + mongoClient.appendMetadata(getInitialMongoDriverInformation()); BsonDocument initialClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); @@ -148,18 +133,21 @@ void shouldAppendToDefaultClientMetadataWhenUpdatedAfterInitialization(@Nullable //when sleep(5); // wait for connection to become idle - updateClientMetadata(driverVersion, driverName, driverPlatform, mongoClient); + mongoClient.appendMetadata(getMongoDriverInformation(driverInformation)); //then BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); BsonDocument updatedDriverInformation = updatedClientMetadata.getDocument("driver"); + String driverName = driverInformation.getDriverName(); + String driverVersion = driverInformation.getDriverVersion(); + String driverPlatform = driverInformation.getDriverPlatform(); String expectedDriverName = driverName == null ? generatedDriverName : generatedDriverName + "|" + driverName; String expectedDriverVersion = driverVersion == null ? generatedVersionName : generatedVersionName + "|" + driverVersion; String expectedDriverPlatform = driverPlatform == null ? generatedPlatformName : generatedPlatformName + "|" + driverPlatform; - assertEquals(updatedDriverInformation.getString("name").getValue(), expectedDriverName); + assertEquals(expectedDriverName, updatedDriverInformation.getString("name").getValue()); assertTrue(updatedDriverInformation.getString("version").getValue().endsWith(expectedDriverVersion)); assertTrue(updatedClientMetadata.getString("platform").getValue().endsWith(expectedDriverPlatform)); @@ -169,31 +157,219 @@ void shouldAppendToDefaultClientMetadataWhenUpdatedAfterInitialization(@Nullable } } - // Not a prose test. Additional test for better coverage. - @Test - void shouldAppendProvidedMetadatDuringInitialization() { + @DisplayName("Test 3: Multiple Successive Metadata Updates with Duplicate Data") + @ParameterizedTest(name = "{index} => {arguments}") + @MethodSource("provideDriverAndFrameworkInformation") + void testMultipleSuccessiveMetadataUpdatesWithDuplicateData(final DriverInformation driverInformation) { //given - MongoDriverInformation initialWrappingLibraryDriverInformation = MongoDriverInformation.builder() - .driverName("library") - .driverVersion("1.2") - .driverPlatform("Library Platform") - .build(); + try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettings())) { + mongoClient.appendMetadata(getInitialMongoDriverInformation()); + + BsonDocument initialClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); - try (MongoClient mongoClient = createMongoClient(initialWrappingLibraryDriverInformation, getMongoClientSettingsBuilder() - .build())) { + BsonDocument generatedDriverInformation = initialClientMetadata.getDocument("driver"); + String generatedDriverName = generatedDriverInformation.get("name").asString().getValue(); + String generatedVersionName = generatedDriverInformation.get("version").asString().getValue(); + String generatedPlatformName = initialClientMetadata.get("platform").asString().getValue(); //when - BsonDocument clientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) - .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); - BsonDocument driverInformation = clientMetadata.getDocument("driver"); + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(driverInformation)); //then - assertTrue(driverInformation.get("name").asString().getValue().endsWith("|library")); - assertTrue(driverInformation.get("version").asString().getValue().endsWith("|1.2")); - assertTrue(clientMetadata.get("platform").asString().getValue().endsWith("|Library Platform")); + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + BsonDocument updatedDriverInformation = updatedClientMetadata.getDocument("driver"); + + String expectedDriverName = generatedDriverName; + String expectedDriverVersion = generatedVersionName; + String expectedDriverPlatform = generatedPlatformName; + + if (!(driverInformation.equals(INITIAL_DRIVER_INFORMATION))) { + expectedDriverName = generatedDriverName + "|" + driverInformation.getDriverName(); + expectedDriverVersion = generatedVersionName + "|" + driverInformation.getDriverVersion(); + expectedDriverPlatform = generatedPlatformName + "|" + driverInformation.getDriverPlatform(); + } + + assertEquals(expectedDriverName, updatedDriverInformation.getString("name").getValue()); + assertTrue(updatedDriverInformation.getString("version").getValue().endsWith(expectedDriverVersion)); + assertTrue(updatedClientMetadata.getString("platform").getValue().endsWith(expectedDriverPlatform)); + } + } + + @DisplayName("Test 4: Multiple Metadata Updates with Duplicate Data") + @Test + void testMultipleMetadataUpdatesWithDuplicateData() { + // given + try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettings())) { + mongoClient.appendMetadata(getMongoDriverInformation(new DriverInformation("library", "1.2", "Library Platform"))); + + //when + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(new DriverInformation("framework", "2.0", "Framework Platform"))); + BsonDocument clientMetaData = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + // then + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(new DriverInformation("library", "1.2", "Library Platform"))); + + // then + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + assertEquals(clientMetaData, updatedClientMetadata); + } + } + + @DisplayName("Test 5: Metadata is not appended if identical to initial metadata") + @Test + void testMetadataIsNotAppendedIfIdenticalToInitialMetadata() { + // given + MongoDriverInformation initialWrappingLibraryDriverInformation = getInitialMongoDriverInformation(); + try (MongoClient mongoClient = createMongoClient(initialWrappingLibraryDriverInformation, getMongoClientSettings())) { + //when + BsonDocument clientMetaData = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + // then + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(new DriverInformation("library", "1.2", "Library Platform"))); + + // then + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + assertEquals(clientMetaData, updatedClientMetadata); } } + @DisplayName("Test 6: Metadata is not appended if identical to initial metadata (separated by non-identical metadata)") + @Test + void testMetadataIsNotAppendedIfIdenticalToInitialMetadataSeparatedByNonIdenticalMetadata() { + // given + MongoDriverInformation initialWrappingLibraryDriverInformation = getInitialMongoDriverInformation(); + try (MongoClient mongoClient = createMongoClient(initialWrappingLibraryDriverInformation, getMongoClientSettings())) { + //when + BsonDocument clientMetaData = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + // then + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(new DriverInformation("library", "1.2", "Library Platform"))); + + // then + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + assertEquals(clientMetaData, updatedClientMetadata); + + // then + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(new DriverInformation("framework", "1.2", "Library Platform"))); + + clientMetaData = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + // then + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(new DriverInformation("library", "1.2", "Library Platform"))); + + updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + assertEquals(clientMetaData, updatedClientMetadata); + } + } + + @DisplayName("Test 7: Empty strings are considered unset when appending duplicate metadata") + @ParameterizedTest(name = "{index} => {arguments}") + @MethodSource("provideDriverInformationWithNullsAndEmptyStrings") + void testEmptyStringsAreConsideredUnsetWhenAppendingDuplicateMetadata( + final DriverInformation initialDriverInformation, + final DriverInformation updatedDriverInformation) { + // given + try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettings())) { + //when + mongoClient.appendMetadata(getMongoDriverInformation(initialDriverInformation)); + + BsonDocument clientMetaData = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + // then + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(updatedDriverInformation)); + + // then + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + assertEquals(clientMetaData, updatedClientMetadata); + } + } + + @DisplayName("Test 8: Empty strings are considered unset when appending metadata identical to initial metadata") + @ParameterizedTest(name = "{index} => {arguments}") + @MethodSource("provideDriverInformationWithNullsAndEmptyStrings") + void testEmptyStringsAreConsideredUnsetWhenAppendingMetadataIdenticalToInitialMetadata( + final DriverInformation initialDriverInformation, + final DriverInformation updatedDriverInformation) { + // given + try (MongoClient mongoClient = createMongoClient(getMongoDriverInformation(initialDriverInformation), getMongoClientSettings())) { + //when + BsonDocument clientMetaData = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + // then + sleep(5); // wait for connection to become idle + mongoClient.appendMetadata(getMongoDriverInformation(updatedDriverInformation)); + + // then + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient) + .orElseThrow(AbstractClientMetadataProseTest::failOnEmptyMetadata); + + assertEquals(clientMetaData, updatedClientMetadata); + } + } + + public static Stream provideDriverInformation() { + return Stream.of( + Arguments.of(new DriverInformation("framework", "2.0", "Framework Platform")), + Arguments.of(new DriverInformation("framework", "2.0", null)), + Arguments.of(new DriverInformation("framework", null, "Framework Platform")), + Arguments.of(new DriverInformation("framework", null, null)) + ); + } + + public static Stream provideDriverAndFrameworkInformation() { + return Stream.of( + Arguments.of(new DriverInformation("library", "1.2", "Library Platform")), + Arguments.of(new DriverInformation("framework", "1.2", "Library Platform")), + Arguments.of(new DriverInformation("library", "2.0", "Library Platform")), + Arguments.of(new DriverInformation("library", "1.2", "Framework Platform")), + Arguments.of(new DriverInformation("framework", "2.0", "Library Platform")), + Arguments.of(new DriverInformation("framework", "1.2", "Framework Platform")), + Arguments.of(new DriverInformation("library", "2.0", "Framework Platform")) + ); + } + + public static Stream provideDriverInformationWithNullsAndEmptyStrings() { + return Stream.of( + Arguments.of(new DriverInformation(null, "1.2", "Library Platform"), new DriverInformation("", "1.2", "Library Platform")), + Arguments.of(new DriverInformation("library", null, "Library Platform"), new DriverInformation("library", "", "Library Platform")), + Arguments.of(new DriverInformation("library", "1.2", null), new DriverInformation("library", "1.2", "")) + ); + } + + + private MongoClientSettings getMongoClientSettings() { + return getMongoClientSettingsBuilder() + .applyToConnectionPoolSettings(builder -> + builder.maxConnectionIdleTime(1, TimeUnit.MILLISECONDS)) + .build(); + } + private Optional executePingAndCaptureMetadataHandshake(final MongoClient mongoClient) { commandListener.reset(); mongoClient.getDatabase("admin") @@ -225,16 +401,18 @@ private static BsonDocument withRemovedKeys(final BsonDocument updatedClientMeta return clone; } - private static void updateClientMetadata(@Nullable final String driverVersion, - @Nullable final String driverName, - @Nullable final String driverPlatform, - final MongoClient mongoClient) { - MongoDriverInformation.Builder builder; - builder = MongoDriverInformation.builder(); - ofNullable(driverName).ifPresent(builder::driverName); - ofNullable(driverVersion).ifPresent(builder::driverVersion); - ofNullable(driverPlatform).ifPresent(builder::driverPlatform); - mongoClient.appendMetadata(builder.build()); + private static final DriverInformation INITIAL_DRIVER_INFORMATION = new DriverInformation("library", "1.2", "Library Platform"); + + private static MongoDriverInformation getInitialMongoDriverInformation() { + return getMongoDriverInformation(INITIAL_DRIVER_INFORMATION); + } + + private static MongoDriverInformation getMongoDriverInformation(final DriverInformation driverInformation) { + MongoDriverInformation.Builder builder = MongoDriverInformation.builder(); + ofNullable(driverInformation.getDriverName()).ifPresent(builder::driverName); + ofNullable(driverInformation.getDriverVersion()).ifPresent(builder::driverVersion); + ofNullable(driverInformation.getDriverPlatform()).ifPresent(builder::driverPlatform); + return builder.build(); } private static AssertionError failOnEmptyMetadata() {