Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
@@ -1,8 +1,6 @@
package eu.bbmri_eric.negotiator.webhook;

import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -76,19 +74,11 @@ public void deleteWebhook(@PathVariable Long id) {
}

@Operation(
summary = "Add a delivery to a webhook",
description = "Adds a new delivery to the specified webhook and returns the response")
@PostMapping(value = "/{id}/deliveries")
public EntityModel<DeliveryDTO> addDelivery(
@PathVariable Long id,
@Valid
@RequestBody
@Schema(
description = "Content to deliver",
example = "{\"test\":\"yes\"}",
type = "object")
String content) {
DeliveryDTO dto = webhookService.deliver(content, WebhookEventType.CUSTOM, id);
summary = "Send a ping delivery to a webhook",
description = "Sends a predefined ping payload to the specified webhook")
@PostMapping(value = "/{id}/ping")
public EntityModel<DeliveryDTO> ping(@PathVariable Long id) {
DeliveryDTO dto = webhookService.ping(id);
return EntityModel.of(dto);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package eu.bbmri_eric.negotiator.webhook;

import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType;
import java.time.Instant;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
Expand All @@ -23,11 +22,9 @@ class WebhookDeliveryDispatcher {
* @param webhookId the id of the target webhook
* @param payload the JSON payload to deliver
* @param eventType the event type header for the delivery
* @param occurredAt the event timestamp to include as a header
*/
@Async("webhookDeliveryExecutor")
void scheduleDelivery(
Long webhookId, String payload, WebhookEventType eventType, Instant occurredAt) {
webhookService.deliver(payload, eventType, webhookId, occurredAt);
void scheduleDelivery(Long webhookId, String payload, WebhookEventType eventType) {
webhookService.deliver(payload, eventType, webhookId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ private void dispatch(WebhookPayloadEnvelope<?> payloadEnvelope) {
}
List<Long> webhookIds = webhookService.getActiveWebhookIds();
for (Long webhookId : webhookIds) {
webhookDeliveryDispatcher.scheduleDelivery(
webhookId, payload, payloadEnvelope.type(), payloadEnvelope.timestamp());
webhookDeliveryDispatcher.scheduleDelivery(webhookId, payload, payloadEnvelope.type());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package eu.bbmri_eric.negotiator.webhook;

import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType;
import java.time.Instant;
import java.util.List;

public interface WebhookService {
Expand Down Expand Up @@ -45,8 +44,7 @@ public interface WebhookService {
void deleteWebhook(Long id);

/**
* Creates a new delivery for a given webhook, using the current time as the occurred-at
* timestamp.
* Creates a new delivery for a given webhook.
*
* @param jsonPayload the JSON content for the delivery
* @param eventType the event type header for the delivery
Expand All @@ -55,6 +53,14 @@ public interface WebhookService {
*/
DeliveryDTO deliver(String jsonPayload, WebhookEventType eventType, Long webhookId);

/**
* Sends a standardized ping delivery for the given webhook.
*
* @param webhookId the id of the target webhook
* @return a DTO representing the newly created ping delivery
*/
DeliveryDTO ping(Long webhookId);

/**
* Creates a manual redelivery for a previously recorded delivery.
*
Expand All @@ -64,18 +70,6 @@ public interface WebhookService {
*/
DeliveryDTO redeliver(Long webhookId, String deliveryId);

/**
* Creates a new delivery for a given webhook with an explicit occurred-at timestamp.
*
* @param jsonPayload the JSON content for the delivery
* @param eventType the event type header for the delivery
* @param webhookId the id of the target webhook
* @param occurredAt the event timestamp to include as a header
* @return a DTO representing the newly created delivery
*/
DeliveryDTO deliver(
String jsonPayload, WebhookEventType eventType, Long webhookId, Instant occurredAt);

/**
* Returns the ids of all currently active webhooks.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package eu.bbmri_eric.negotiator.webhook;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.bbmri_eric.negotiator.common.JSONUtils;
import eu.bbmri_eric.negotiator.common.exceptions.EntityNotFoundException;
Expand Down Expand Up @@ -113,7 +112,22 @@ public void deleteWebhook(Long id) {

@Override
public DeliveryDTO deliver(String jsonPayload, WebhookEventType eventType, Long webhookId) {
return deliver(jsonPayload, eventType, webhookId, Instant.now());
if (!JSONUtils.isJSONValid(jsonPayload)) {
throw new IllegalArgumentException("Content is not a valid JSON");
}
Webhook webhook = getWebhook(webhookId);
RestTemplate restTemplate =
webhook.isSslVerification() ? secureRestTemplate : insecureRestTemplate;
String webhookMessageId = UUID.randomUUID().toString();
return deliverWebhook(jsonPayload, restTemplate, webhook, eventType, null, webhookMessageId);
}

@Override
public DeliveryDTO ping(Long webhookId) {
Instant occurredAt = Instant.now();
var payloadEnvelope = WebhookPayloadEnvelope.ping(webhookId, occurredAt);
String payload = serializePayload(payloadEnvelope);
return deliver(payload, WebhookEventType.PING, webhookId);
}

@Override
Expand Down Expand Up @@ -142,21 +156,6 @@ public DeliveryDTO redeliver(Long webhookId, String deliveryId) {
rootDeliveryId);
}

@Override
public DeliveryDTO deliver(
String jsonPayload, WebhookEventType eventType, Long webhookId, Instant occurredAt) {
String payloadToDeliver = toPayloadEnvelopeIfNeeded(jsonPayload, eventType, occurredAt);
if (!JSONUtils.isJSONValid(payloadToDeliver)) {
throw new IllegalArgumentException("Content is not a valid JSON");
}
Webhook webhook = getWebhook(webhookId);
RestTemplate restTemplate =
webhook.isSslVerification() ? secureRestTemplate : insecureRestTemplate;
String webhookMessageId = UUID.randomUUID().toString();
return deliverWebhook(
payloadToDeliver, restTemplate, webhook, eventType, null, webhookMessageId);
}

@Override
@Transactional(readOnly = true)
public List<Long> getActiveWebhookIds() {
Expand Down Expand Up @@ -271,17 +270,11 @@ private static int postWebhook(
return new HttpEntity<>(jsonPayload, headers);
}

private String toPayloadEnvelopeIfNeeded(
String jsonPayload, @NonNull WebhookEventType eventType, @NonNull Instant occurredAt) {
if (eventType != WebhookEventType.CUSTOM) {
return jsonPayload;
}

private String serializePayload(Object payloadObject) {
try {
JsonNode customData = objectMapper.readTree(jsonPayload);
return objectMapper.writeValueAsString(WebhookPayloadEnvelope.custom(occurredAt, customData));
return objectMapper.writeValueAsString(payloadObject);
} catch (JsonProcessingException ex) {
throw new IllegalArgumentException("Content is not a valid JSON", ex);
throw new IllegalStateException("Could not serialize webhook payload", ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package eu.bbmri_eric.negotiator.webhook.event;

/**
* Webhook data payload for a ping delivery.
*
* @param webhookId identifier of the target webhook
*/
record PingWebhookEvent(Long webhookId) {}
Comment thread
stetsche marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
/** Defines external webhook event type names. */
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum WebhookEventType {
CUSTOM("custom"),
NEGOTIATION_ADDED("negotiation.added"),
NEGOTIATION_STATE_UPDATED("negotiation.state.updated"),
NEGOTIATION_INFO_UPDATED("negotiation.info.updated"),
NEGOTIATION_POST_ADDED("negotiation.post.added"),
NEGOTIATION_RESOURCE_ADDED("negotiation.resource.added"),
NEGOTIATION_RESOURCE_STATE_UPDATED("negotiation.resource.state.updated"),
NEGOTIATION_POST_ADDED("negotiation.post.added");
NEGOTIATION_STATE_UPDATED("negotiation.state.updated"),
PING("ping");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
*/
public record WebhookPayloadEnvelope<T>(WebhookEventType type, Instant timestamp, T data) {

/** Creates a custom outbound payload envelope. */
public static <T> WebhookPayloadEnvelope<T> custom(Instant occurredAt, T data) {
return new WebhookPayloadEnvelope<>(WebhookEventType.CUSTOM, occurredAt, data);
/** Creates a ping outbound payload envelope. */
public static WebhookPayloadEnvelope<?> ping(Long webhookId, Instant occurredAt) {

Check failure on line 15 in backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookPayloadEnvelope.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove usage of generic wildcard type.

See more on https://sonarcloud.io/project/issues?id=BBMRI-ERIC_negotiator&issues=AZ4bAXNZ0bi4G5nkfG80&open=AZ4bAXNZ0bi4G5nkfG80&pullRequest=1163
return new WebhookPayloadEnvelope<>(
WebhookEventType.PING, occurredAt, new PingWebhookEvent(webhookId));
}
}
Loading
Loading