-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SDK functionality to make it easier to deserialize/consume EventGrid events #2426
Changes from 1 commit
d5ea61e
3d44634
e677125
902e748
613a617
42e2c97
8b21ddc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ | |
*/ | ||
package com.microsoft.azure.eventgrid.customization; | ||
|
||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.microsoft.azure.eventgrid.models.EventGridEvent; | ||
import com.microsoft.azure.management.apigeneration.Beta; | ||
|
@@ -15,23 +14,28 @@ | |
import java.io.IOException; | ||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Type; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
/** | ||
* The type that can be used to de-serialize events. | ||
*/ | ||
@Beta | ||
public class EventGridSubscriber { | ||
/** | ||
* The default adapter for to be used for de-serializing the events | ||
* The default adapter to be used for de-serializing the events. | ||
*/ | ||
private final AzureJacksonAdapter defaultSerializerAdapter; | ||
/** | ||
* The map containing user defined mapping of eventType to Java model type | ||
* The map containing user defined mapping of eventType to Java model type. | ||
*/ | ||
private Map<String, Type> eventTypeToEventDataMapping; | ||
|
||
/** | ||
* Creates EventGridSubscriber with default de-serializer. | ||
*/ | ||
@Beta | ||
public EventGridSubscriber() { | ||
this.defaultSerializerAdapter = new AzureJacksonAdapter(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the Data property contains polymorphic types, will this handle that as well, or do we need some special logic for it? In the C# case, we do something like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the tests, I do see you are supporting this, which is great! However, it wasn't quite clear to me whether this is handled automatically, or whether like in C# we need to express something like the above where we add the PolymorphicDeserializeJsonConverter explicitly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. De-serialization of polymorphic types are handled using Jackson annotations. See this models There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, thanks. For 1st party / system event data that contains polymorphic fields, will we have to annotate in the same way (on top of the default autorest generated class)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For system events - if discriminator is defined in swagger for corresponding event models then auto rest will generate these annotations, no need to modify the generated models. For custom events - User has to annotate their model as shown in the tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sounds good, thanks! |
||
|
@@ -40,114 +44,124 @@ public EventGridSubscriber() { | |
|
||
/** | ||
* Add a custom event mapping. If a mapping with same eventType exists then the old eventDataType is replaced by | ||
* the specified eventDataType | ||
* the specified eventDataType. | ||
* | ||
* @param eventType the event type name | ||
* @param eventDataType type of the Java model that the event type name mapped to | ||
* @param eventType the event type name. | ||
* @param eventDataType type of the Java model that the event type name mapped to. | ||
*/ | ||
@Beta | ||
public void putCustomEventMapping(String eventType, Type eventDataType) { | ||
public void putCustomEventMapping(final String eventType, final Type eventDataType) { | ||
if (eventType == null || eventType.isEmpty()) { | ||
throw new IllegalArgumentException("eventType parameter is required and cannot be null or empty"); | ||
} | ||
if (eventDataType == null) { | ||
throw new IllegalArgumentException("eventDataType parameter is required and cannot be null"); | ||
} | ||
this.eventTypeToEventDataMapping.put(eventType.toLowerCase(), eventDataType); | ||
this.eventTypeToEventDataMapping.put(canonicalizeEventType(eventType), eventDataType); | ||
} | ||
|
||
/** | ||
* Get type of the Java model that is mapped to the given eventType. | ||
* | ||
* @param eventType the event type name | ||
* @return type of the Java model id mapping exists, null otherwise | ||
* @param eventType the event type name. | ||
* @return type of the Java model if mapping exists, null otherwise. | ||
*/ | ||
@Beta | ||
public Type getCustomEventMapping(String eventType) { | ||
if (eventType == null || eventType.isEmpty()) { | ||
throw new IllegalArgumentException("eventType parameter is required and cannot be null or empty"); | ||
} | ||
if (!this.eventTypeToEventDataMapping.containsKey(eventType.toLowerCase())) { | ||
public Type getCustomEventMapping(final String eventType) { | ||
if (!containsCustomEventMappingFor(eventType)) { | ||
return null; | ||
} else { | ||
return this.eventTypeToEventDataMapping.get(canonicalizeEventType(eventType)); | ||
} | ||
return this.eventTypeToEventDataMapping.get(eventType.toLowerCase()); | ||
} | ||
|
||
/** | ||
* @return get all registered custom event mappings. | ||
*/ | ||
@Beta | ||
public Set<Map.Entry<String, Type>> getAllCustomEventMapping() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Should this have a "s" at the end to denote the plurality (getAllCustomEventMappings())? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for catching the typo, will fix it. |
||
return Collections.unmodifiableSet(this.eventTypeToEventDataMapping.entrySet()); | ||
} | ||
|
||
/** | ||
* Removes the mapping with the given eventType. | ||
* | ||
* @param eventType the event type name | ||
* @return true if the mapping exists and removed, false if mapping does not exists | ||
* @param eventType the event type name. | ||
* @return true if the mapping exists and removed, false if mapping does not exists. | ||
*/ | ||
@Beta | ||
public boolean removeCustomEventMapping(String eventType) { | ||
if (eventType == null || eventType.isEmpty()) { | ||
throw new IllegalArgumentException("eventType parameter is required and cannot be null or empty"); | ||
} | ||
if (!this.eventTypeToEventDataMapping.containsKey(eventType.toLowerCase())) { | ||
public boolean removeCustomEventMapping(final String eventType) { | ||
if (!containsCustomEventMappingFor(eventType)) { | ||
return false; | ||
} else { | ||
this.eventTypeToEventDataMapping.remove(canonicalizeEventType(eventType)); | ||
return true; | ||
} | ||
this.eventTypeToEventDataMapping.remove(eventType.toLowerCase()); | ||
return true; | ||
} | ||
|
||
/** | ||
* Checks an event mapping with the given eventType exists. | ||
* Checks if an event mapping with the given eventType exists. | ||
* | ||
* @param eventType the event type name | ||
* @return true if the mapping exists, false otherwise | ||
* @param eventType the event type name. | ||
* @return true if the mapping exists, false otherwise. | ||
*/ | ||
@Beta | ||
public boolean containsEventMappingFor(String eventType) { | ||
public boolean containsCustomEventMappingFor(final String eventType) { | ||
if (eventType == null || eventType.isEmpty()) { | ||
throw new IllegalArgumentException("eventType parameter is required and cannot be null or empty"); | ||
return false; | ||
} else { | ||
return this.eventTypeToEventDataMapping.containsKey(canonicalizeEventType(eventType)); | ||
} | ||
return this.eventTypeToEventDataMapping.containsKey(eventType.toLowerCase()); | ||
} | ||
|
||
/** | ||
* De-serialize the events in the given requested content using default de-serializer. | ||
* | ||
* @param requestContent the request content in string format | ||
* @param requestContent the request content in string format. | ||
* @return De-serialized events. | ||
* | ||
* @throws IOException | ||
*/ | ||
@Beta | ||
public EventGridEvent[] DeserializeEventGridEvents(final String requestContent) throws IOException { | ||
return this.DeserializeEventGridEvents(requestContent, this.defaultSerializerAdapter); | ||
public EventGridEvent[] deserializeEventGridEvents(final String requestContent) throws IOException { | ||
return this.deserializeEventGridEvents(requestContent, this.defaultSerializerAdapter); | ||
} | ||
|
||
/** | ||
* De-serialize the events in the given requested content using the provided de-serializer. | ||
* | ||
* @param requestContent the request content in string format | ||
* @param serializerAdapter the de-serializer | ||
* @return e-serialized events. | ||
* @param requestContent the request content as string. | ||
* @param serializerAdapter the de-serializer. | ||
* @return de-serialized events. | ||
* @throws IOException | ||
*/ | ||
@Beta | ||
public EventGridEvent[] DeserializeEventGridEvents(final String requestContent, final SerializerAdapter<ObjectMapper> serializerAdapter) throws IOException { | ||
public EventGridEvent[] deserializeEventGridEvents(final String requestContent, final SerializerAdapter<ObjectMapper> serializerAdapter) throws IOException { | ||
EventGridEvent[] eventGridEvents = serializerAdapter.<EventGridEvent[]>deserialize(requestContent, EventGridEvent[].class); | ||
for (EventGridEvent receivedEvent : eventGridEvents) { | ||
if (receivedEvent.data() == null) { | ||
continue; | ||
} else { | ||
final String dataStr = serializerAdapter.serializeRaw(receivedEvent.data()); | ||
final String eventType = receivedEvent.eventType(); | ||
final Type eventDataType; | ||
if (SystemEventTypeMappings.containsMappingFor(eventType)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: can we do something like if (SystemEventTypeMappings.containsMappingFor(eventType) { eventDataType = SystemEventTypeMappings.getMapping(EventType) } else { eventDataType = getCustomEventMapping(eventType);} and then actually call deserialize & setEventData just once outside the if/else block. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For custom events, the Data property can be of any type (i.e. object, array, or any other primitive type), so we would need some special handling for it. For C#, the way we handle it is covered in 185-207 of https://github.com/Azure/azure-sdk-for-net/blob/psSdkJson6/src/SDKs/EventGrid/DataPlane/Microsoft.Azure.EventGrid/Customization/EventGridSubscriber.cs. Not sure if the current logic automatically handles that case as well, wanted to double-check. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
final Object eventData = serializerAdapter.<Object>deserialize(dataStr, SystemEventTypeMappings.getMapping(eventType)); | ||
setEventData(receivedEvent, eventData); | ||
} else if (containsEventMappingFor(eventType)) { | ||
final Object eventData = serializerAdapter.<Object>deserialize(dataStr, getCustomEventMapping(eventType)); | ||
eventDataType = SystemEventTypeMappings.getMapping(eventType); | ||
} else if (containsCustomEventMappingFor(eventType)) { | ||
eventDataType = getCustomEventMapping(eventType); | ||
} else { | ||
eventDataType = null; | ||
} | ||
if (eventDataType != null) { | ||
final String eventDataAsString = serializerAdapter.serializeRaw(receivedEvent.data()); | ||
final Object eventData = serializerAdapter.<Object>deserialize(eventDataAsString, eventDataType); | ||
setEventData(receivedEvent, eventData); | ||
} | ||
} | ||
} | ||
return eventGridEvents; | ||
} | ||
|
||
private void setEventData(EventGridEvent event, Object data) { | ||
private static void setEventData(EventGridEvent event, final Object data) { | ||
// This reflection based way to set the data field needs to be removed once | ||
// we expose a wither in EventGridEvent to set the data. (Check swagger + codegen) | ||
try { | ||
|
@@ -160,4 +174,12 @@ private void setEventData(EventGridEvent event, Object data) { | |
throw new RuntimeException(iae); | ||
} | ||
} | ||
|
||
private static String canonicalizeEventType(final String eventType) { | ||
if (eventType == null) { | ||
return null; | ||
} else { | ||
return eventType.toLowerCase(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a method to list all custom event mappings (similar to what we have in https://raw.githubusercontent.com/Azure/azure-sdk-for-net/psSdkJson6/src/SDKs/EventGrid/DataPlane/Microsoft.Azure.EventGrid/Customization/EventGridSubscriber.cs)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, will add it.