33
44package com .example .graphwebhook ;
55
6+ import java .io .IOException ;
7+ import java .net .URI ;
8+ import java .net .URISyntaxException ;
9+ import java .util .List ;
610import java .util .Objects ;
7- import java .util .concurrent .CompletableFuture ;
8- import javax .annotation .Nonnull ;
911import com .corundumstudio .socketio .AckRequest ;
1012import com .corundumstudio .socketio .SocketIOClient ;
1113import com .corundumstudio .socketio .SocketIONamespace ;
1214import com .corundumstudio .socketio .SocketIOServer ;
1315import com .corundumstudio .socketio .listener .DataListener ;
14- import com .microsoft . graph . logger . DefaultLogger ;
15- import com .microsoft . graph . models . ChangeNotification ;
16- import com .microsoft . graph . models . ChangeNotificationCollection ;
16+ import com .example . graphwebhook . notifications . ChangeNotification ;
17+ import com .example . graphwebhook . notifications . ChangeNotificationCollection ;
18+ import com .example . graphwebhook . notifications . ChangeNotificationEncryptedContent ;
1719import com .microsoft .graph .models .ChatMessage ;
1820import com .microsoft .graph .models .Message ;
19- import com .microsoft .graph .serializer .DefaultSerializer ;
20-
21+ import com .microsoft .kiota .HttpMethod ;
22+ import com .microsoft .kiota .RequestInformation ;
23+ import com .microsoft .kiota .serialization .KiotaJsonSerialization ;
2124import org .slf4j .Logger ;
2225import org .slf4j .LoggerFactory ;
2326import org .springframework .beans .factory .annotation .Autowired ;
@@ -97,50 +100,54 @@ public ResponseEntity<String> handleValidation(
97100 * @return A 202 Accepted response
98101 */
99102 @ PostMapping ("/listen" )
100- public CompletableFuture <ResponseEntity <String >> handleNotification (
101- @ RequestBody @ Nonnull final String jsonPayload ) {
102- // Deserialize the JSON body into a ChangeNotificationCollection
103- final var serializer = new DefaultSerializer (new DefaultLogger ());
104- final var notifications =
105- serializer .deserializeObject (jsonPayload , ChangeNotificationCollection .class );
106-
107- if (notifications == null ) {
108- return CompletableFuture .completedFuture (ResponseEntity .accepted ().body ("" ));
109- }
103+ public ResponseEntity <String > handleNotification (
104+ @ RequestBody @ NonNull final String jsonPayload ) {
105+ try {
106+ // Deserialize the JSON body into a ChangeNotificationCollection
107+ final ChangeNotificationCollection notifications =
108+ KiotaJsonSerialization .deserialize (jsonPayload ,
109+ ChangeNotificationCollection ::createFromDiscriminatorValue );
110+
111+ if (notifications == null ) {
112+ return ResponseEntity .accepted ().body ("" );
113+ }
110114
111- // Check for validation tokens
112- boolean areTokensValid = true ;
113- if ( notifications . validationTokens != null
114- && ! Objects . requireNonNull ( notifications . validationTokens ) .isEmpty ()) {
115- areTokensValid = TokenHelper .areValidationTokensValid (new String [] {clientId },
116- new String [] {tenantId }, Objects .requireNonNull (notifications . validationTokens ),
117- Objects .requireNonNull (keyDiscoveryUrl ));
118- }
115+ // Check for validation tokens
116+ boolean areTokensValid = true ;
117+ final List < String > validationTokens = notifications . getValidationTokens ();
118+ if ( validationTokens != null && validationTokens .isEmpty ()) {
119+ areTokensValid = TokenHelper .areValidationTokensValid (new String [] {clientId },
120+ new String [] {tenantId }, Objects .requireNonNull (validationTokens ),
121+ Objects .requireNonNull (keyDiscoveryUrl ));
122+ }
119123
120- if (areTokensValid ) {
121- for (ChangeNotification notification : Objects .requireNonNull (notifications .value )) {
122- // Look up subscription in store
123- var subscription = subscriptionStore .getSubscription (
124- Objects .requireNonNull (notification .subscriptionId ).toString ());
125-
126- // Only process if we know about this subscription AND
127- // the client state in the notification matches
128- if (subscription != null
129- && subscription .clientState .equals (notification .clientState )) {
130- if (notification .encryptedContent == null ) {
131- // No encrypted content, this is a new message notification
132- // without resource data
133- processNewMessageNotification (notification , subscription );
134- } else {
135- // With encrypted content, this is a new channel message
136- // notification with encrypted resource data
137- processNewChannelMessageNotification (notification , subscription );
124+ if (areTokensValid ) {
125+ for (ChangeNotification notification : Objects .requireNonNull (notifications .getValue ())) {
126+ // Look up subscription in store
127+ var subscription = subscriptionStore .getSubscription (
128+ Objects .requireNonNull (notification .getSubscriptionId ()));
129+
130+ // Only process if we know about this subscription AND
131+ // the client state in the notification matches
132+ if (subscription != null
133+ && subscription .clientState .equals (notification .getClientState ())) {
134+ if (notification .getEncryptedContent () == null ) {
135+ // No encrypted content, this is a new message notification
136+ // without resource data
137+ processNewMessageNotification (notification , subscription );
138+ } else {
139+ // With encrypted content, this is a new channel message
140+ // notification with encrypted resource data
141+ processNewChannelMessageNotification (notification , subscription );
142+ }
138143 }
139144 }
140145 }
146+ } catch (IOException e ) {
147+ e .printStackTrace ();
141148 }
142149
143- return CompletableFuture . completedFuture ( ResponseEntity .accepted ().body ("" ) );
150+ return ResponseEntity .accepted ().body ("" );
144151 }
145152
146153
@@ -164,12 +171,24 @@ private void processNewMessageNotification(@NonNull final ChangeNotification not
164171 // so use the customRequest method instead of the fluent API
165172 // Once message has been retrieved, send the information via SocketIO
166173 // to subscribed clients
167- graphClient .customRequest ("/" + notification .resource , Message .class ).buildRequest ()
168- .getAsync ().thenAccept (message -> {
169- if (message != null )
170- socketIONamespace .getRoomOperations (subscription .subscriptionId ).sendEvent (
171- "notificationReceived" , new NewMessageNotification (message ));
172- });
174+
175+ final RequestInformation request = new RequestInformation ();
176+ request .httpMethod = HttpMethod .GET ;
177+ URI messageUri ;
178+ try {
179+ messageUri = new URI (
180+ graphClient .getRequestAdapter ().getBaseUrl () + "/" + notification .getResource ());
181+ } catch (URISyntaxException e ) {
182+ e .printStackTrace ();
183+ return ;
184+ }
185+
186+ request .setUri (messageUri );
187+ final Message message = graphClient .getRequestAdapter ().send (request , null ,
188+ Message ::createFromDiscriminatorValue );
189+ if (message != null )
190+ socketIONamespace .getRoomOperations (subscription .subscriptionId )
191+ .sendEvent ("notificationReceived" , new NewMessageNotification (message ));
173192 }
174193
175194
@@ -183,24 +202,30 @@ private void processNewChannelMessageNotification(
183202 @ NonNull final ChangeNotification notification ,
184203 @ NonNull final SubscriptionRecord subscription ) {
185204 // Decrypt the encrypted key from the notification
205+ final ChangeNotificationEncryptedContent encryptedContent =
206+ Objects .requireNonNull (notification .getEncryptedContent ());
186207 final var decryptedKey = Objects .requireNonNull (certificateStore
187- .getEncryptionKey (Objects . requireNonNull ( notification . encryptedContent ). dataKey ));
208+ .getEncryptionKey (encryptedContent . getDataKey () ));
188209
189210 // Validate the signature
211+ final String data = encryptedContent .getData ();
190212 if (certificateStore .isDataSignatureValid (decryptedKey ,
191- Objects . requireNonNull ( notification . encryptedContent ). data ,
192- Objects . requireNonNull ( notification . encryptedContent ). dataSignature )) {
213+ data ,
214+ encryptedContent . getDataSignature () )) {
193215 // Decrypt the data using the decrypted key
194- final var decryptedData = certificateStore .getDecryptedData (decryptedKey ,
195- Objects .requireNonNull (notification .encryptedContent ).data );
216+ final var decryptedData = certificateStore .getDecryptedData (decryptedKey , data );
196217
197218 // Deserialize the decrypted JSON into a ChatMessage
198- final var serializer = new DefaultSerializer (new DefaultLogger ());
199- final var chatMessage = Objects .requireNonNull (serializer
200- .deserializeObject (Utilities .ensureNonNull (decryptedData ), ChatMessage .class ));
201- // Send the information to subscribed clients
202- socketIONamespace .getRoomOperations (subscription .subscriptionId )
219+ ChatMessage chatMessage ;
220+ try {
221+ chatMessage = KiotaJsonSerialization .deserialize (decryptedData ,
222+ ChatMessage ::createFromDiscriminatorValue );
223+ // Send the information to subscribed clients
224+ socketIONamespace .getRoomOperations (subscription .subscriptionId )
203225 .sendEvent ("notificationReceived" , new NewChatMessageNotification (chatMessage ));
226+ } catch (IOException e ) {
227+ e .printStackTrace ();
228+ }
204229 }
205230 }
206231}
0 commit comments