Compare commits

...

6 Commits

Author SHA1 Message Date
Christoph Strobl
492f09fbdf Add isReadable method to UnwrappedMongoPersistentProperty.
Closes: #4489
2023-08-31 09:43:58 +02:00
Julia
e3e73f5351 Fix #self @DocumentReference resolution when used in constructor.
This commit enables document reference lookup to use `DocumentReferenceSource` to properly instantiate an entity containig a @DocumentReference `#self` property.

Closes #4484
Original Pull Request: #4486
2023-08-31 07:57:44 +02:00
Mark Paluch
e5aff2645b Polishing.
Refactor duplicate code into callback.

See #4481
2023-08-24 14:01:28 +02:00
Mark Paluch
b3c0fbb02d Guard command completion listener against unsupported observation context.
We now no longer attempt to complete the Observation if the context is not a MongoDB one. For commands that target the admin database and run within a parent observation, we still might have an Observation but that one points to the parent invocation and not the MongoDB one as we do not record commands for the admin database.

Closes #4481
2023-08-24 14:01:28 +02:00
Julia Lee
51de522e88 After release cleanups.
See #4450
2023-08-18 08:59:39 -04:00
Julia Lee
d2842b246f Prepare next development iteration.
See #4450
2023-08-18 08:59:35 -04:00
9 changed files with 167 additions and 46 deletions

16
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.2.0-M2</version>
<version>4.2.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.2.0-M2</version>
<version>3.2.0-SNAPSHOT</version>
</parent>
<modules>
@@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>3.2.0-M2</springdata.commons>
<springdata.commons>3.2.0-SNAPSHOT</springdata.commons>
<mongo>4.10.2</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
@@ -144,6 +144,16 @@
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/milestone</url>

View File

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

View File

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

View File

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

View File

@@ -98,6 +98,7 @@ import com.mongodb.DBRef;
* @author Roman Puchkovskiy
* @author Heesu Jung
* @author Divya Srivastava
* @author Julia Lee
*/
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
@@ -1976,8 +1977,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (property.isDocumentReference()) {
return (T) dbRefResolver.resolveReference(property, accessor.get(property), referenceLookupDelegate,
context::convert);
return (T) dbRefResolver.resolveReference(property,
new DocumentReferenceSource(accessor.getDocument(), accessor.get(property)),
referenceLookupDelegate, context::convert);
}
return super.getPropertyValue(property);

View File

@@ -255,6 +255,11 @@ class UnwrappedMongoPersistentProperty implements MongoPersistentProperty {
return delegate.isWritable();
}
@Override
public boolean isReadable() {
return delegate.isReadable();
}
@Override
public boolean isImmutable() {
return delegate.isImmutable();

View File

@@ -15,6 +15,12 @@
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import java.util.function.BiConsumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
@@ -27,10 +33,6 @@ import com.mongodb.event.CommandListener;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
/**
* Implement MongoDB's {@link CommandListener} using Micrometer's {@link Observation} API.
*
@@ -126,50 +128,54 @@ public class MongoObservationCommandListener implements CommandListener {
@Override
public void commandSucceeded(CommandSucceededEvent event) {
RequestContext requestContext = event.getRequestContext();
doInObservation(event.getRequestContext(), (observation, context) -> {
if (requestContext == null) {
return;
}
context.setCommandSucceededEvent(event);
Observation observation = requestContext.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
if (observation == null) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Command succeeded - will stop observation [" + observation + "]");
}
MongoHandlerContext context = (MongoHandlerContext) observation.getContext();
context.setCommandSucceededEvent(event);
if (log.isDebugEnabled()) {
log.debug("Command succeeded - will stop observation [" + observation + "]");
}
observation.stop();
observation.stop();
});
}
@Override
public void commandFailed(CommandFailedEvent event) {
RequestContext requestContext = event.getRequestContext();
doInObservation(event.getRequestContext(), (observation, context) -> {
context.setCommandFailedEvent(event);
if (log.isDebugEnabled()) {
log.debug("Command failed - will stop observation [" + observation + "]");
}
observation.error(event.getThrowable());
observation.stop();
});
}
/**
* Performs the given action for the {@link Observation} and {@link MongoHandlerContext} if there is an ongoing Mongo
* Observation. Exceptions thrown by the action are relayed to the caller.
*
* @param requestContext the context to extract the Observation from.
* @param action the action to invoke.
*/
private void doInObservation(@Nullable RequestContext requestContext,
BiConsumer<Observation, MongoHandlerContext> action) {
if (requestContext == null) {
return;
}
Observation observation = requestContext.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
if (observation == null) {
if (observation == null || !(observation.getContext()instanceof MongoHandlerContext context)) {
return;
}
MongoHandlerContext context = (MongoHandlerContext) observation.getContext();
context.setCommandFailedEvent(event);
if (log.isDebugEnabled()) {
log.debug("Command failed - will stop observation [" + observation + "]");
}
observation.error(event.getThrowable());
observation.stop();
action.accept(observation, context);
}
/**

View File

@@ -57,6 +57,7 @@ import com.mongodb.client.model.Filters;
* {@link DocumentReference} related integration tests for {@link MongoTemplate}.
*
* @author Christoph Strobl
* @author Julia Lee
*/
@ExtendWith(MongoClientExtension.class)
public class MongoTemplateDocumentReferenceTests {
@@ -1265,6 +1266,32 @@ public class MongoTemplateDocumentReferenceTests {
.isEqualTo(new ObjectRefHavingStringIdTargetType(id.toHexString(), "me-the-referenced-object"));
}
@Test // GH-4484
void resolveReferenceForOneToManyLookupWithSelfVariableWhenUsedInCtorArgument() {
OneToManyStylePublisherWithRequiredArgsCtor publisher = new OneToManyStylePublisherWithRequiredArgsCtor("p-100", null);
template.save(publisher);
OneToManyStyleBook book1 = new OneToManyStyleBook();
book1.id = "id-1";
book1.publisherId = publisher.id;
OneToManyStyleBook book2 = new OneToManyStyleBook();
book2.id = "id-2";
book2.publisherId = "p-200";
OneToManyStyleBook book3 = new OneToManyStyleBook();
book3.id = "id-3";
book3.publisherId = publisher.id;
template.save(book1);
template.save(book2);
template.save(book3);
OneToManyStylePublisherWithRequiredArgsCtor target = template.findOne(query(where("id").is(publisher.id)), OneToManyStylePublisherWithRequiredArgsCtor.class);
assertThat(target.books).containsExactlyInAnyOrder(book1, book3);
}
static class SingleRefRoot {
String id;
@@ -2249,4 +2276,40 @@ public class MongoTemplateDocumentReferenceTests {
return "MongoTemplateDocumentReferenceTests.WithListOfRefs(id=" + this.getId() + ", refs=" + this.getRefs() + ")";
}
}
static class OneToManyStylePublisherWithRequiredArgsCtor {
@Id
String id;
@ReadOnlyProperty
@DocumentReference(lookup="{'publisherId':?#{#self._id} }")
List<OneToManyStyleBook> books;
public OneToManyStylePublisherWithRequiredArgsCtor(String id, List<OneToManyStyleBook> books) {
this.id = id;
this.books = books;
}
public String getId() {
return this.id;
}
public List<OneToManyStyleBook> getBooks() {
return this.books;
}
public void setId(String id) {
this.id = id;
}
public void setBooks(List<OneToManyStyleBook> books) {
this.books = books;
}
public String toString() {
return "MongoTemplateDocumentReferenceTests.OneToManyStylePublisherWithRequiredArgsCtor(id=" + this.getId() + ", book="
+ this.getBooks() + ")";
}
}
}

View File

@@ -16,6 +16,15 @@
package org.springframework.data.mongodb.observability;
import static io.micrometer.core.tck.MeterRegistryAssert.*;
import static org.mockito.Mockito.*;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import org.bson.BsonDocument;
import org.bson.BsonString;
@@ -33,18 +42,12 @@ import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
/**
* Series of test cases exercising {@link MongoObservationCommandListener}.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @author Mark Paluch
*/
class MongoObservationCommandListenerTests {
@@ -176,6 +179,38 @@ class MongoObservationCommandListenerTests {
assertThatTimerRegisteredWithTags();
}
@Test // GH-4481
void completionShouldIgnoreIncompatibleObservationContext() {
// given
RequestContext traceRequestContext = getContext();
Observation observation = mock(Observation.class);
traceRequestContext.put(ObservationThreadLocalAccessor.KEY, observation);
// when
listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "insert", null, 0));
verify(observation).getContext();
verifyNoMoreInteractions(observation);
}
@Test // GH-4481
void failureShouldIgnoreIncompatibleObservationContext() {
// given
RequestContext traceRequestContext = getContext();
Observation observation = mock(Observation.class);
traceRequestContext.put(ObservationThreadLocalAccessor.KEY, observation);
// when
listener.commandFailed(new CommandFailedEvent(traceRequestContext, 0, null, "insert", 0, null));
verify(observation).getContext();
verifyNoMoreInteractions(observation);
}
private RequestContext getContext() {
return ((SynchronousContextProvider) ContextProviderFactory.create(observationRegistry)).getContext();
}