Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -336,6 +337,9 @@ public String getSpecVersion() {
* @return the extension attributes as an unmodifiable map.
*/
public Map<String, Object> getExtensionAttributes() {
if (this.cloudEvent.getAdditionalProperties() == null) {
return null;
}
return Collections.unmodifiableMap(this.cloudEvent.getAdditionalProperties());
}

Expand All @@ -348,6 +352,9 @@ public Map<String, Object> getExtensionAttributes() {
* @return the cloud event itself.
*/
public CloudEvent addExtensionAttribute(String name, Object value) {
if (this.cloudEvent.getAdditionalProperties() == null) {
this.cloudEvent.setAdditionalProperties(new HashMap<>());
}
this.cloudEvent.getAdditionalProperties().put(name.toLowerCase(Locale.ENGLISH), value);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
import com.azure.core.http.rest.Response;
import com.azure.core.util.Context;
import com.azure.core.util.serializer.SerializerAdapter;
import com.azure.core.util.tracing.TracerProxy;
import com.azure.messaging.eventgrid.implementation.Constants;
import com.azure.messaging.eventgrid.implementation.EventGridPublisherClientImpl;
import com.azure.messaging.eventgrid.implementation.EventGridPublisherClientImplBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static com.azure.core.util.FluxUtil.withContext;
import static com.azure.core.util.tracing.Tracer.AZ_TRACING_NAMESPACE_KEY;

/**
* A service client that publishes events to an EventGrid topic or domain. Use {@link EventGridPublisherClientBuilder}
Expand All @@ -33,6 +36,9 @@ public final class EventGridPublisherAsyncClient {

private final EventGridServiceVersion serviceVersion;

// Please see <a href=https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-services-resource-providers>here</a>
// for more information on Azure resource provider namespaces.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how this resource provide namespace link is relevant to the constructor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for a constant, which is copied to Constants.java but I didn't copy this together. Good catch.


Comment thread
samvaity marked this conversation as resolved.
Outdated
EventGridPublisherAsyncClient(HttpPipeline pipeline, String hostname, SerializerAdapter serializerAdapter,
EventGridServiceVersion serviceVersion) {
this.impl = new EventGridPublisherClientImplBuilder()
Expand Down Expand Up @@ -70,7 +76,8 @@ Mono<Void> sendEvents(Iterable<EventGridEvent> events, Context context) {
return Flux.fromIterable(events)
.map(EventGridEvent::toImpl)
.collectList()
.flatMap(list -> this.impl.publishEventsAsync(this.hostname, list, context));
.flatMap(list -> this.impl.publishEventsAsync(this.hostname,
list, context.addData(AZ_TRACING_NAMESPACE_KEY, Constants.EVENT_GRID_TRACING_NAMESPACE_VALUE)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have a null check for context before using it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This internal api is called by a public API, which calls FluxUtil.withContext to create a Context. So I assume the context won't be null.
In debugging, I see it's an empty Context instance instead of null.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YijunXieMS - this method is also called from sync client and the user can pass a null context.

User can call sendEventsWithResponse(events, null)

    public Response<Void> sendEventsWithResponse(Iterable<EventGridEvent> events, Context context) {
        return asyncClient.sendEventsWithResponse(events, context).block();
    }

}

/**
Expand All @@ -85,10 +92,12 @@ public Mono<Void> sendCloudEvents(Iterable<CloudEvent> events) {
}

Mono<Void> sendCloudEvents(Iterable<CloudEvent> events, Context context) {
this.addCloudEventTracePlaceHolder(events);
return Flux.fromIterable(events)
.map(CloudEvent::toImpl)
.collectList()
.flatMap(list -> this.impl.publishCloudEventEventsAsync(this.hostname, list, context));
.flatMap(list -> this.impl.publishCloudEventEventsAsync(this.hostname, list,
context.addData(AZ_TRACING_NAMESPACE_KEY, Constants.EVENT_GRID_TRACING_NAMESPACE_VALUE)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to check context for null on the calling function to avoid NPE when doing addData

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does withContext guarantee context to be not null?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases, we end up calling this from the sync client and hence the context could be null. Need a check there when passing.

}

/**
Expand All @@ -105,7 +114,8 @@ public Mono<Void> sendCustomEvents(Iterable<Object> events) {
Mono<Void> sendCustomEvents(Iterable<Object> events, Context context) {
return Flux.fromIterable(events)
.collectList()
.flatMap(list -> this.impl.publishCustomEventEventsAsync(this.hostname, list, context));
.flatMap(list -> this.impl.publishCustomEventEventsAsync(this.hostname, list,
context.addData(AZ_TRACING_NAMESPACE_KEY, Constants.EVENT_GRID_TRACING_NAMESPACE_VALUE)));
}

/**
Expand All @@ -123,7 +133,8 @@ Mono<Response<Void>> sendEventsWithResponse(Iterable<EventGridEvent> events, Con
return Flux.fromIterable(events)
.map(EventGridEvent::toImpl)
.collectList()
.flatMap(list -> this.impl.publishEventsWithResponseAsync(this.hostname, list, context));
.flatMap(list -> this.impl.publishEventsWithResponseAsync(this.hostname, list,
context.addData(AZ_TRACING_NAMESPACE_KEY, Constants.EVENT_GRID_TRACING_NAMESPACE_VALUE)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my comment about user passing null for context applies to this method.

}

/**
Expand All @@ -138,10 +149,12 @@ public Mono<Response<Void>> sendCloudEventsWithResponse(Iterable<CloudEvent> eve
}

Mono<Response<Void>> sendCloudEventsWithResponse(Iterable<CloudEvent> events, Context context) {
this.addCloudEventTracePlaceHolder(events);
return Flux.fromIterable(events)
.map(CloudEvent::toImpl)
.collectList()
.flatMap(list -> this.impl.publishCloudEventEventsWithResponseAsync(this.hostname, list, context));
.flatMap(list -> this.impl.publishCloudEventEventsWithResponseAsync(this.hostname, list,
context.addData(AZ_TRACING_NAMESPACE_KEY, Constants.EVENT_GRID_TRACING_NAMESPACE_VALUE)));
}

/**
Expand All @@ -158,6 +171,21 @@ public Mono<Response<Void>> sendCustomEventsWithResponse(Iterable<Object> events
Mono<Response<Void>> sendCustomEventsWithResponse(Iterable<Object> events, Context context) {
return Flux.fromIterable(events)
.collectList()
.flatMap(list -> this.impl.publishCustomEventEventsWithResponseAsync(this.hostname, list, context));
.flatMap(list -> this.impl.publishCustomEventEventsWithResponseAsync(this.hostname, list,
context.addData(AZ_TRACING_NAMESPACE_KEY, Constants.EVENT_GRID_TRACING_NAMESPACE_VALUE)));
}

private void addCloudEventTracePlaceHolder(Iterable<CloudEvent> events) {
if (TracerProxy.isTracingEnabled()) {
for (CloudEvent event : events) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

events can be null since the public APIs don't seem to check. It might be better to have the null check and include an error message to indicate that events cannot be null.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added null check like in EventHubs

if (event.getExtensionAttributes() == null ||
(event.getExtensionAttributes().get(Constants.TRACE_PARENT) == null &&
event.getExtensionAttributes().get(Constants.TRACE_STATE) == null)) {

event.addExtensionAttribute(Constants.TRACE_PARENT, Constants.TRACE_PARENT_PLACEHOLDER);
event.addExtensionAttribute(Constants.TRACE_STATE, Constants.TRACE_STATE_PLACEHOLDER);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need both?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put the two placeholder separately. After placeholder replacement, whatever in the request headers will be put into the body eventually. There won't be two in the header has just one.

}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.serializer.JacksonAdapter;
import com.azure.core.util.serializer.SerializerAdapter;
import com.azure.core.util.tracing.TracerProxy;
import com.azure.messaging.eventgrid.implementation.CloudEventTracingPipelinePolicy;

import java.net.MalformedURLException;
import java.net.URL;
Expand Down Expand Up @@ -144,6 +146,9 @@ public EventGridPublisherAsyncClient buildAsyncClient() {

HttpPolicyProviders.addAfterRetryPolicies(httpPipelinePolicies);

if (TracerProxy.isTracingEnabled()) {
httpPipelinePolicies.add(new CloudEventTracingPipelinePolicy());
}
httpPipelinePolicies.add(new HttpLoggingPolicy(httpLogOptions));

HttpPipeline buildPipeline = new HttpPipelineBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.azure.messaging.eventgrid.implementation;

import com.azure.core.http.HttpHeader;
import com.azure.core.http.HttpPipelineCallContext;
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.HttpResponse;
import com.azure.core.http.policy.HttpPipelinePolicy;
import com.azure.core.util.tracing.TracerProxy;
import reactor.core.publisher.Mono;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;


public class CloudEventTracingPipelinePolicy implements HttpPipelinePolicy {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add javadoc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@Override
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
final HttpRequest request = context.getHttpRequest();
final HttpHeader contentType = request.getHeaders().get(Constants.CONTENT_TYPE);
if (TracerProxy.isTracingEnabled() && contentType != null &&
Constants.CLOUD_EVENT_CONTENT_TYPE.equals(contentType.getValue())) {
return request.getBody().map(byteBuffer ->
replaceTracingPlaceHolder(request, byteBuffer)).then(next.process());
}
else {
return next.process();
}
}

static String replaceTracingPlaceHolder(HttpRequest request, ByteBuffer byteBuffer) {
String bodyString = new String(byteBuffer.array(), StandardCharsets.UTF_8);

@srnagar srnagar Oct 1, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the Flux stream have strings that are split across multiple ByteBuffer boundaries?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to use a StringBuilder to take everything from the Flux. Good question.

final HttpHeader traceparentHeader = request.getHeaders().get(Constants.TRACE_PARENT);
final HttpHeader tracestateHeader = request.getHeaders().get(Constants.TRACE_STATE);
bodyString = bodyString.replace(Constants.TRACE_PARENT_REPLACE,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not very clear why we are replacing the trace names. It might be good to add some documentation for this method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Javadoc and some comments.

traceparentHeader != null
? String.format(",\"%s\":\"%s\"", Constants.TRACE_PARENT,
traceparentHeader.getValue()) : "");
bodyString = bodyString.replace(Constants.TRACE_STATE_REPLACE,
tracestateHeader != null
? String.format(",\"%s\":\"%s\"", Constants.TRACE_STATE, tracestateHeader.getValue()) : "");
request.setHeader(Constants.CONTENT_LENGTH, String.valueOf(bodyString.length()));
request.setBody(bodyString);
return bodyString;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.azure.messaging.eventgrid.implementation;

public class Constants {
public static final String CONTENT_TYPE = "Content-Type";
public static final String CONTENT_LENGTH = "Content-Length";
public static final String CLOUD_EVENT_CONTENT_TYPE = "application/cloudevents-batch+json; charset=utf-8";
public static final String TRACE_PARENT = "traceparent";
public static final String TRACE_STATE = "tracestate";
public static final String TRACE_PARENT_PLACEHOLDER = "TP-14b6b15b-74b6-4178-847e-d142aa2727b2";
public static final String TRACE_STATE_PLACEHOLDER = "TS-14b6b15b-74b6-4178-847e-d142aa2727b2";
public static final String TRACE_PARENT_REPLACE = ",\"" + TRACE_PARENT + "\":\"TP-14b6b15b-74b6-4178-847e-d142aa2727b2\"";
public static final String TRACE_STATE_REPLACE = ",\"" + TRACE_STATE + "\":\"TS-14b6b15b-74b6-4178-847e-d142aa2727b2\"";
public static final String EVENT_GRID_TRACING_NAMESPACE_VALUE = "Microsoft.EventGrid";
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class AcsChatMessageEventBaseProperties extends AcsChatEventBasePropertie
* The version of the message
*/
@JsonProperty(value = "version")
private Integer version;
private Long version;
Comment thread
samvaity marked this conversation as resolved.

/**
* Get the messageId property: The chat message id.
Expand Down Expand Up @@ -152,7 +152,7 @@ public AcsChatMessageEventBaseProperties setType(String type) {
*
* @return the version value.
*/
public Integer getVersion() {
public Long getVersion() {
return this.version;
}

Expand All @@ -162,7 +162,7 @@ public Integer getVersion() {
* @param version the version value to set.
* @return the AcsChatMessageEventBaseProperties object itself.
*/
public AcsChatMessageEventBaseProperties setVersion(Integer version) {
public AcsChatMessageEventBaseProperties setVersion(Long version) {
this.version = version;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class AcsChatThreadEventBaseProperties extends AcsChatEventBaseProperties
* The version of the thread
*/
@JsonProperty(value = "version")
private Integer version;
private Long version;

/**
* Get the createTime property: The original creation time of the thread.
Expand All @@ -48,7 +48,7 @@ public AcsChatThreadEventBaseProperties setCreateTime(OffsetDateTime createTime)
*
* @return the version value.
*/
public Integer getVersion() {
public Long getVersion() {
return this.version;
}

Expand All @@ -58,7 +58,7 @@ public Integer getVersion() {
* @param version the version value to set.
* @return the AcsChatThreadEventBaseProperties object itself.
*/
public AcsChatThreadEventBaseProperties setVersion(Integer version) {
public AcsChatThreadEventBaseProperties setVersion(Long version) {
this.version = version;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.azure.messaging.eventgrid.implementation;

import com.azure.core.http.HttpMethod;
import com.azure.core.http.HttpRequest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;

public class CloudEventTracingPipelinePolicyTests {
private HttpRequest httpRequest;

@BeforeEach
public void setup() throws MalformedURLException {
httpRequest = new HttpRequest(HttpMethod.POST, new URL("https://something.com"));
}

@Test
void processBodyWithNoHeader() {
String testBodyString = "[{\"id\":\"313ac785-2dca-467e-a6a7-623f1baa2890\",\"source\":\"source\"," +
"\"type\":\"json\",\"specversion\":\"1.0\",\"tracestate\":\"TS-14b6b15b-74b6-4178-847e-d142aa2727b2\"," +
"\"traceparent\":\"TP-14b6b15b-74b6-4178-847e-d142aa2727b2\"}]";
String expectedNewBody = "[{\"id\":\"313ac785-2dca-467e-a6a7-623f1baa2890\",\"source\":\"source\"," +
"\"type\":\"json\",\"specversion\":\"1.0\"}]";

httpRequest.setBody(testBodyString);
httpRequest.setHeader(Constants.CONTENT_LENGTH, testBodyString);
String newBody = CloudEventTracingPipelinePolicy.replaceTracingPlaceHolder(
httpRequest, ByteBuffer.wrap(testBodyString.getBytes()));
Assertions.assertEquals(expectedNewBody, newBody);
Assertions.assertEquals(httpRequest.getHeaders().get(Constants.CONTENT_LENGTH).getValue(),
String.valueOf(newBody.length()));
}

@Test
void processBodyWithTraceParentHeader() {
httpRequest.setHeader(Constants.TRACE_PARENT, "aTraceParent");
String testBodyString = "[{\"id\":\"313ac785-2dca-467e-a6a7-623f1baa2890\",\"source\":\"source\"," +
"\"type\":\"json\",\"specversion\":\"1.0\",\"tracestate\":\"TS-14b6b15b-74b6-4178-847e-d142aa2727b2\"," +
"\"traceparent\":\"TP-14b6b15b-74b6-4178-847e-d142aa2727b2\"}]";
String expectedNewBody = "[{\"id\":\"313ac785-2dca-467e-a6a7-623f1baa2890\",\"source\":\"source\"," +
"\"type\":\"json\",\"specversion\":\"1.0\"," +
"\"traceparent\":\"aTraceParent\"}]";
httpRequest.setBody(testBodyString);
httpRequest.setHeader(Constants.CONTENT_LENGTH, testBodyString);
String newBody = CloudEventTracingPipelinePolicy.replaceTracingPlaceHolder(
httpRequest, ByteBuffer.wrap(testBodyString.getBytes()));
Assertions.assertEquals(expectedNewBody, newBody);
Assertions.assertEquals(httpRequest.getHeaders().get(Constants.CONTENT_LENGTH).getValue(),
String.valueOf(newBody.length()));
}

@Test
void processBodyWithTraceStateHeader() {
httpRequest.setHeader(Constants.TRACE_STATE, "aTraceState");
String testBodyString = "[{\"id\":\"313ac785-2dca-467e-a6a7-623f1baa2890\",\"source\":\"source\"," +
"\"type\":\"json\",\"specversion\":\"1.0\",\"tracestate\":\"TS-14b6b15b-74b6-4178-847e-d142aa2727b2\"," +
"\"traceparent\":\"TP-14b6b15b-74b6-4178-847e-d142aa2727b2\"}]";
String expectedNewBody = "[{\"id\":\"313ac785-2dca-467e-a6a7-623f1baa2890\",\"source\":\"source\"," +
"\"type\":\"json\",\"specversion\":\"1.0\"," +
"\"tracestate\":\"aTraceState\"}]";

httpRequest.setBody(testBodyString);
httpRequest.setHeader(Constants.CONTENT_LENGTH, testBodyString);
String newBody = CloudEventTracingPipelinePolicy.replaceTracingPlaceHolder(
httpRequest, ByteBuffer.wrap(testBodyString.getBytes()));
Assertions.assertEquals(expectedNewBody, newBody);
Assertions.assertEquals(httpRequest.getHeaders().get(Constants.CONTENT_LENGTH).getValue(),
String.valueOf(newBody.length()));
}
}