diff --git a/docs-core/pom.xml b/docs-core/pom.xml
index 64fbea77..28071930 100644
--- a/docs-core/pom.xml
+++ b/docs-core/pom.xml
@@ -116,6 +116,11 @@
com.sun.mail
javax.mail
+
+
+ com.squareup.okhttp3
+ okhttp
+
diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/WebhookDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/WebhookDao.java
index 78eecefe..cfd16ccb 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/dao/WebhookDao.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/dao/WebhookDao.java
@@ -36,6 +36,10 @@ public class WebhookDao {
sb.append(" from T_WEBHOOK w ");
// Add search criterias
+ if (criteria.getEvent() != null) {
+ criteriaList.add("w.WHK_EVENT_C = :event");
+ parameterMap.put("event", criteria.getEvent().name());
+ }
criteriaList.add("w.WHK_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/criteria/WebhookCriteria.java b/docs-core/src/main/java/com/sismics/docs/core/dao/criteria/WebhookCriteria.java
index d93091e5..a7675e79 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/dao/criteria/WebhookCriteria.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/dao/criteria/WebhookCriteria.java
@@ -1,9 +1,24 @@
package com.sismics.docs.core.dao.criteria;
+import com.sismics.docs.core.constant.WebhookEvent;
+
/**
* Webhook criteria.
*
* @author bgamard
*/
public class WebhookCriteria {
+ /**
+ * Webhook event.
+ */
+ private WebhookEvent event;
+
+ public WebhookEvent getEvent() {
+ return event;
+ }
+
+ public WebhookCriteria setEvent(WebhookEvent event) {
+ this.event = event;
+ return this;
+ }
}
diff --git a/docs-core/src/main/java/com/sismics/docs/core/listener/async/WebhookAsyncListener.java b/docs-core/src/main/java/com/sismics/docs/core/listener/async/WebhookAsyncListener.java
new file mode 100644
index 00000000..b95f5360
--- /dev/null
+++ b/docs-core/src/main/java/com/sismics/docs/core/listener/async/WebhookAsyncListener.java
@@ -0,0 +1,103 @@
+package com.sismics.docs.core.listener.async;
+
+import com.google.common.collect.Lists;
+import com.google.common.eventbus.AllowConcurrentEvents;
+import com.google.common.eventbus.Subscribe;
+import com.sismics.docs.core.constant.WebhookEvent;
+import com.sismics.docs.core.dao.WebhookDao;
+import com.sismics.docs.core.dao.criteria.WebhookCriteria;
+import com.sismics.docs.core.dao.dto.WebhookDto;
+import com.sismics.docs.core.event.*;
+import com.sismics.docs.core.util.TransactionUtil;
+import okhttp3.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Listener for triggering webhooks.
+ *
+ * @author bgamard
+ */
+public class WebhookAsyncListener {
+ /**
+ * Logger.
+ */
+ private static final Logger log = LoggerFactory.getLogger(WebhookAsyncListener.class);
+
+ /**
+ * OkHttp client.
+ */
+ private static final OkHttpClient client = new OkHttpClient();
+ public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+
+ @Subscribe
+ @AllowConcurrentEvents
+ public void on(final DocumentCreatedAsyncEvent event) {
+ triggerWebhook(WebhookEvent.DOCUMENT_CREATED, event.getDocument().getId());
+ }
+
+ @Subscribe
+ @AllowConcurrentEvents
+ public void on(final DocumentUpdatedAsyncEvent event) {
+ triggerWebhook(WebhookEvent.DOCUMENT_UPDATED, event.getDocumentId());
+ }
+
+ @Subscribe
+ @AllowConcurrentEvents
+ public void on(final DocumentDeletedAsyncEvent event) {
+ triggerWebhook(WebhookEvent.DOCUMENT_UPDATED, event.getDocumentId());
+ }
+
+ @Subscribe
+ @AllowConcurrentEvents
+ public void on(final FileCreatedAsyncEvent event) {
+ triggerWebhook(WebhookEvent.FILE_CREATED, event.getFile().getId());
+ }
+
+ @Subscribe
+ @AllowConcurrentEvents
+ public void on(final FileUpdatedAsyncEvent event) {
+ triggerWebhook(WebhookEvent.FILE_UPDATED, event.getFile().getId());
+ }
+
+ @Subscribe
+ @AllowConcurrentEvents
+ public void on(final FileDeletedAsyncEvent event) {
+ triggerWebhook(WebhookEvent.FILE_DELETED, event.getFile().getId());
+ }
+
+ /**
+ * Trigger the webhooks for the specified event.
+ *
+ * @param event Event
+ * @param id ID
+ */
+ private void triggerWebhook(WebhookEvent event, String id) {
+ List webhookUrlList = Lists.newArrayList();
+
+ TransactionUtil.handle(() -> {
+ WebhookDao webhookDao = new WebhookDao();
+ List webhookDtoList = webhookDao.findByCriteria(new WebhookCriteria().setEvent(event), null);
+ for (WebhookDto webhookDto : webhookDtoList) {
+ webhookUrlList.add(webhookDto.getUrl());
+ }
+ });
+
+ RequestBody body = RequestBody.create(JSON, "{\"event\": \"" + event.name() + "\", \"id\": \"" + id + "\"}");
+
+ for (String webhookUrl : webhookUrlList) {
+ Request request = new Request.Builder()
+ .url(webhookUrl)
+ .post(body)
+ .build();
+ try (Response response = client.newCall(request).execute()) {
+ log.info("Successfully called the webhook at: " + webhookUrl + " - " + response.code());
+ } catch (IOException e) {
+ log.error("Error calling the webhook at: " + webhookUrl, e);
+ }
+ }
+ }
+}
diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java
index 1193530c..57ef11bc 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java
@@ -145,6 +145,7 @@ public class AppContext {
asyncEventBus.register(new RebuildIndexAsyncListener());
asyncEventBus.register(new AclCreatedAsyncListener());
asyncEventBus.register(new AclDeletedAsyncListener());
+ asyncEventBus.register(new WebhookAsyncListener());
mailEventBus = newAsyncEventBus();
mailEventBus.register(new PasswordLostAsyncListener());
diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/WebhookResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/WebhookResource.java
index 51b2dc69..97f74251 100644
--- a/docs-web/src/main/java/com/sismics/docs/rest/resource/WebhookResource.java
+++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/WebhookResource.java
@@ -67,6 +67,7 @@ public class WebhookResource extends BaseResource {
* Add a webhook.
*
* @api {put} /webhook Add a webhook
+ * @apiDescription Each time the specified event is raised, the webhook URL will be POST-ed with the following JSON payload: {"event": "Event name", "id": "ID of the document or file"}
* @apiName PutWebhook
* @apiWebhook Webhook
* @apiParam {String="DOCUMENT_CREATED","DOCUMENT_UPDATED","DOCUMENT_DELETED","FILE_CREATED","FILE_UPDATED","FILE_DELETED"} event Event
diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestWebhookResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestWebhookResource.java
index f3c81222..08a34a34 100644
--- a/docs-web/src/test/java/com/sismics/docs/rest/TestWebhookResource.java
+++ b/docs-web/src/test/java/com/sismics/docs/rest/TestWebhookResource.java
@@ -1,5 +1,6 @@
package com.sismics.docs.rest;
+import com.sismics.docs.rest.resource.ThirdPartyWebhookResource;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import org.junit.Assert;
import org.junit.Test;
@@ -8,6 +9,7 @@ import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
+import java.util.Date;
/**
@@ -23,6 +25,10 @@ public class TestWebhookResource extends BaseJerseyTest {
public void testWebhookResource() {
// Login admin
String adminToken = clientUtil.login("admin", "admin", false);
+
+ // Login webhook1
+ clientUtil.createUser("webhook1");
+ String webhook1Token = clientUtil.login("webhook1");
// Get all webhooks
JsonObject json = target().path("/webhook")
@@ -37,7 +43,21 @@ public class TestWebhookResource extends BaseJerseyTest {
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.put(Entity.form(new Form()
.param("event", "DOCUMENT_CREATED")
- .param("url", "https://www.sismics.com")), JsonObject.class);
+ .param("url", "http://localhost:" + getPort() + "/docs/thirdpartywebhook")), JsonObject.class);
+
+ // Create a document
+ json = target().path("/document").request()
+ .cookie(TokenBasedSecurityFilter.COOKIE_NAME, webhook1Token)
+ .put(Entity.form(new Form()
+ .param("title", "Webhook document 1")
+ .param("language", "eng")
+ .param("create_date", Long.toString(new Date().getTime()))), JsonObject.class);
+ String document1Id = json.getString("id");
+
+ // Check the webhook payload
+ JsonObject payload = ThirdPartyWebhookResource.getLastPayload();
+ Assert.assertEquals("DOCUMENT_CREATED", payload.getString("event"));
+ Assert.assertEquals(document1Id, payload.getString("id"));
// Get all webhooks
json = target().path("/webhook")
@@ -49,7 +69,7 @@ public class TestWebhookResource extends BaseJerseyTest {
JsonObject webhook = webhooks.getJsonObject(0);
String webhookId = webhook.getString("id");
Assert.assertEquals("DOCUMENT_CREATED", webhook.getString("event"));
- Assert.assertEquals("https://www.sismics.com", webhook.getString("url"));
+ Assert.assertEquals("http://localhost:" + getPort() + "/docs/thirdpartywebhook", webhook.getString("url"));
Assert.assertNotNull(webhook.getJsonNumber("create_date"));
// Delete a webhook
diff --git a/docs-web/src/test/java/com/sismics/docs/rest/resource/ThirdPartyWebhookResource.java b/docs-web/src/test/java/com/sismics/docs/rest/resource/ThirdPartyWebhookResource.java
new file mode 100644
index 00000000..b3082b00
--- /dev/null
+++ b/docs-web/src/test/java/com/sismics/docs/rest/resource/ThirdPartyWebhookResource.java
@@ -0,0 +1,34 @@
+package com.sismics.docs.rest.resource;
+
+import javax.json.JsonObject;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+/**
+ * Webhook REST resources.
+ *
+ * @author bgamard
+ */
+@Path("/thirdpartywebhook")
+public class ThirdPartyWebhookResource extends BaseResource {
+ /**
+ * Last payload received.
+ */
+ private static JsonObject lastPayload;
+
+ /**
+ * Add a webhook.
+ *
+ * @return Response
+ */
+ @POST
+ public Response webhook(JsonObject request) {
+ lastPayload = request;
+ return Response.ok().build();
+ }
+
+ public static JsonObject getLastPayload() {
+ return lastPayload;
+ }
+}
diff --git a/pom.xml b/pom.xml
index b346cfee..3525f792 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,7 @@
1.5.7
1.5.6
1.11.2
+ 3.11.0
9.3.11.v20160721
9.3.11.v20160721
@@ -224,6 +225,12 @@
${org.jsoup.jsoup.version}
+
+ com.squareup.okhttp3
+ okhttp
+ ${com.squareup.okhttp3.okhttp.version}
+
+
log4j
log4j