This is an automated email from the ASF dual-hosted git repository.

avikg pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 169b01d  FINERACT-1241-elastic-web-hook-update (#1488)
169b01d is described below

commit 169b01d72b68eb116d911f63dfd921c3f23696ca
Author: Manoj <56669674+fynma...@users.noreply.github.com>
AuthorDate: Tue Nov 10 03:44:27 2020 +0530

    FINERACT-1241-elastic-web-hook-update (#1488)
---
 fineract-provider/dependencies.gradle              |  3 +-
 .../SynchronousCommandProcessingService.java       | 90 +++++++++++++++++++---
 .../core/data/CommandProcessingResult.java         |  7 ++
 .../infrastructure/hooks/api/HookApiConstants.java |  2 +
 .../processor/ElasticSearchHookProcessor.java      | 84 ++++++++++++++++++++
 .../hooks/processor/HookProcessorProvider.java     |  3 +
 .../hooks/processor/ProcessorHelper.java           | 19 +++++
 .../useradministration/domain/AppUser.java         | 12 +++
 .../core_db/V364__elastic_hook_template.sql        | 33 ++++++++
 9 files changed, 242 insertions(+), 11 deletions(-)

diff --git a/fineract-provider/dependencies.gradle 
b/fineract-provider/dependencies.gradle
index 1a3a5e2..f7ef521 100644
--- a/fineract-provider/dependencies.gradle
+++ b/fineract-provider/dependencies.gradle
@@ -79,7 +79,8 @@ dependencies {
             'io.swagger.core.v3:swagger-annotations',
             'org.webjars:webjars-locator-core',
 
-            'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.1.0'
+            'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.1.0',
+            'com.squareup.retrofit2:converter-gson'
             )
     implementation ('org.apache.activemq:activemq-broker') {
         exclude group: 'org.apache.geronimo.specs'
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
index 910b8b7..cb01262 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
@@ -18,8 +18,15 @@
  */
 package org.apache.fineract.commands.service;
 
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.time.Instant;
 import java.time.ZonedDateTime;
+import java.util.HashMap;
 import java.util.Map;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
 import org.apache.fineract.commands.domain.CommandSource;
 import org.apache.fineract.commands.domain.CommandSourceRepository;
 import org.apache.fineract.commands.domain.CommandWrapper;
@@ -38,6 +45,8 @@ import 
org.apache.fineract.infrastructure.hooks.event.HookEvent;
 import org.apache.fineract.infrastructure.hooks.event.HookEventSource;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Service;
@@ -46,6 +55,7 @@ import 
org.springframework.transaction.annotation.Transactional;
 @Service
 public class SynchronousCommandProcessingService implements 
CommandProcessingService {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(SynchronousCommandProcessingService.class);
     private PlatformSecurityContext context;
     private final ApplicationContext applicationContext;
     private final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
@@ -80,7 +90,14 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
 
         final NewCommandSourceHandler handler = findCommandHandler(wrapper);
 
-        final CommandProcessingResult result = handler.processCommand(command);
+        final CommandProcessingResult result;
+        try {
+            result = handler.processCommand(command);
+        } catch (Throwable t) {
+            // publish error event
+            publishErrorEvent(wrapper, command, t);
+            throw t;
+        }
 
         final AppUser maker = this.context.authenticatedUser(wrapper);
 
@@ -125,7 +142,7 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
         }
         result.setRollbackTransaction(null);
 
-        publishEvent(wrapper.entityName(), wrapper.actionName(), result);
+        publishEvent(wrapper.entityName(), wrapper.actionName(), command, 
result);
 
         return result;
     }
@@ -206,18 +223,71 @@ public class SynchronousCommandProcessingService 
implements CommandProcessingSer
         return rollbackTransaction;
     }
 
-    private void publishEvent(final String entityName, final String 
actionName, final CommandProcessingResult result) {
+    private void publishErrorEvent(CommandWrapper wrapper, JsonCommand 
command, Throwable t) {
+
+        ErrorInfo ex;
+        if (t instanceof RuntimeException) {
+            final RuntimeException e = (RuntimeException) t;
+            ex = ErrorHandler.handler(e);
+        } else {
+            ex = new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + 
"}");
+        }
+
+        publishEvent(wrapper.entityName(), wrapper.actionName(), command, ex);
+    }
+
+    private void publishEvent(final String entityName, final String 
actionName, JsonCommand command, final Object result) {
+        Gson gson = new Gson();
+        try {
+            final String authToken = ThreadLocalContextUtil.getAuthToken();
+            final String tenantIdentifier = 
ThreadLocalContextUtil.getTenant().getTenantIdentifier();
+            final AppUser appUser = 
this.context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, 
null, null));
+
+            final HookEventSource hookEventSource = new 
HookEventSource(entityName, actionName);
 
-        final String authToken = ThreadLocalContextUtil.getAuthToken();
-        final String tenantIdentifier = 
ThreadLocalContextUtil.getTenant().getTenantIdentifier();
-        final AppUser appUser = 
this.context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, 
null, null));
+            // TODO: Add support for publishing array events
+            if (command.json() != null && command.json().startsWith("{")) {
+                Type type = new TypeToken<Map<String, Object>>() {}.getType();
+                Map<String, Object> myMap = gson.fromJson(command.json(), 
type);
 
-        final HookEventSource hookEventSource = new 
HookEventSource(entityName, actionName);
+                Map<String, Object> reqmap = new HashMap<>();
+                reqmap.put("entityName", entityName);
+                reqmap.put("actionName", actionName);
+                reqmap.put("createdBy", context.authenticatedUser().getId());
+                reqmap.put("createdByName", 
context.authenticatedUser().getUsername());
+                reqmap.put("createdByFullName", 
context.authenticatedUser().getDisplayName());
 
-        final String serializedResult = 
this.toApiResultJsonSerializer.serialize(result);
+                reqmap.put("request", myMap);
+                if (result instanceof CommandProcessingResult) {
+                    CommandProcessingResult resultCopy = 
CommandProcessingResult
+                            
.fromCommandProcessingResult((CommandProcessingResult) result);
 
-        final HookEvent applicationEvent = new HookEvent(hookEventSource, 
serializedResult, tenantIdentifier, appUser, authToken);
+                    reqmap.put("officeId", resultCopy.getOfficeId());
+                    reqmap.put("clientId", resultCopy.getClientId());
+                    resultCopy.setOfficeId(null);
+                    reqmap.put("response", resultCopy);
+                } else if (result instanceof ErrorInfo) {
+                    ErrorInfo ex = (ErrorInfo) result;
+                    reqmap.put("status", "Exception");
 
-        applicationContext.publishEvent(applicationEvent);
+                    Map<String, Object> errorMap = 
gson.fromJson(ex.getMessage(), type);
+                    errorMap.put("errorCode", ex.getErrorCode());
+                    errorMap.put("statusCode", ex.getStatusCode());
+
+                    reqmap.put("response", errorMap);
+                }
+
+                reqmap.put("timestamp", Instant.now().toString());
+
+                final String serializedResult = 
this.toApiResultJsonSerializer.serialize(reqmap);
+
+                final HookEvent applicationEvent = new 
HookEvent(hookEventSource, serializedResult, tenantIdentifier, appUser, 
authToken);
+
+                applicationContext.publishEvent(applicationEvent);
+            }
+        } catch (Exception e) {
+            LOG.error("Error", e);
+        }
     }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
index 526ebbd..707b8db 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java
@@ -44,6 +44,13 @@ public class CommandProcessingResult implements Serializable 
{
     private final Long glimId;
     private Boolean rollbackTransaction;
 
+    public static CommandProcessingResult 
fromCommandProcessingResult(CommandProcessingResult commandResult) {
+        return new CommandProcessingResult(commandResult.commandId, 
commandResult.officeId, commandResult.groupId, commandResult.clientId,
+                commandResult.loanId, commandResult.savingsId, 
commandResult.resourceIdentifier, commandResult.resourceId,
+                commandResult.transactionId, commandResult.changes, 
commandResult.productId, commandResult.gsimId, commandResult.glimId,
+                commandResult.rollbackTransaction, 
commandResult.subResourceId);
+    }
+
     public static CommandProcessingResult fromDetails(final Long commandId, 
final Long officeId, final Long groupId, final Long clientId,
             final Long loanId, final Long savingsId, final String 
resourceIdentifier, final Long entityId, final Long gsimId,
             final Long glimId, final String transactionId, final Map<String, 
Object> changes, final Long productId,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java
index 36cc1b1..c11deea 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/api/HookApiConstants.java
@@ -38,6 +38,8 @@ public final class HookApiConstants {
 
     public static final String webTemplateName = "Web";
 
+    public static final String elasticSearchTemplateName = "Elastic Search";
+
     public static final String smsTemplateName = "SMS Bridge";
 
     public static final String payloadURLName = "Payload URL";
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ElasticSearchHookProcessor.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ElasticSearchHookProcessor.java
new file mode 100644
index 0000000..551a53b
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ElasticSearchHookProcessor.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.hooks.processor;
+
+import static 
org.apache.fineract.infrastructure.hooks.api.HookApiConstants.contentTypeName;
+import static 
org.apache.fineract.infrastructure.hooks.api.HookApiConstants.payloadURLName;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.fineract.infrastructure.hooks.domain.Hook;
+import org.apache.fineract.infrastructure.hooks.domain.HookConfiguration;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import retrofit2.Callback;
+
+@Service
+public class ElasticSearchHookProcessor implements HookProcessor {
+
+    @Autowired
+    private ProcessorHelper processorHelper;
+
+    @Override
+    public void process(final Hook hook, @SuppressWarnings("unused") final 
AppUser appUser, final String payload, final String entityName,
+            final String actionName, final String tenantIdentifier, final 
String authToken) {
+
+        final Set<HookConfiguration> config = hook.getHookConfig();
+
+        String url = "";
+        String contentType = "";
+
+        for (final HookConfiguration conf : config) {
+            final String fieldName = conf.getFieldName();
+            if (fieldName.equals(payloadURLName)) {
+                url = conf.getFieldValue();
+            }
+            if (fieldName.equals(contentTypeName)) {
+                contentType = conf.getFieldValue();
+            }
+        }
+
+        sendRequest(url, contentType, payload, entityName, actionName, 
tenantIdentifier, authToken);
+
+    }
+
+    @SuppressWarnings("unchecked")
+    private void sendRequest(final String url, final String contentType, final 
String payload, final String entityName,
+            final String actionName, final String tenantIdentifier, 
@SuppressWarnings("unused") final String authToken) {
+
+        final String fineractEndpointUrl = System.getProperty("baseUrl");
+        final WebHookService service = 
processorHelper.createWebHookService(url);
+
+        @SuppressWarnings("rawtypes")
+        final Callback callback = processorHelper.createCallback(url, payload);
+
+        if (contentType.equalsIgnoreCase("json") || 
contentType.contains("json")) {
+            final JsonObject json = new Gson().fromJson(payload, 
JsonObject.class);
+            service.sendJsonRequest(entityName, actionName, tenantIdentifier, 
fineractEndpointUrl, json).enqueue(callback);
+        } else {
+            Map<String, String> map = new HashMap<>();
+            map = new Gson().fromJson(payload, map.getClass());
+            service.sendFormRequest(entityName, actionName, tenantIdentifier, 
fineractEndpointUrl, map).enqueue(callback);
+        }
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java
index bd4e73f..23d5709 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/HookProcessorProvider.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.infrastructure.hooks.processor;
 
+import static 
org.apache.fineract.infrastructure.hooks.api.HookApiConstants.elasticSearchTemplateName;
 import static 
org.apache.fineract.infrastructure.hooks.api.HookApiConstants.smsTemplateName;
 import static 
org.apache.fineract.infrastructure.hooks.api.HookApiConstants.webTemplateName;
 
@@ -44,6 +45,8 @@ public class HookProcessorProvider implements 
ApplicationContextAware {
             processor = this.applicationContext.getBean("twilioHookProcessor", 
TwilioHookProcessor.class);
         } else if (templateName.equals(webTemplateName)) {
             processor = this.applicationContext.getBean("webHookProcessor", 
WebHookProcessor.class);
+        } else if (templateName.equals(elasticSearchTemplateName)) {
+            processor = 
this.applicationContext.getBean("elasticSearchHookProcessor", 
ElasticSearchHookProcessor.class);
         } else {
             processor = null;
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java
index 142a352..3e58455 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java
@@ -34,6 +34,7 @@ import org.springframework.stereotype.Service;
 import retrofit2.Call;
 import retrofit2.Callback;
 import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
 
 @Service
 public final class ProcessorHelper {
@@ -116,7 +117,25 @@ public final class ProcessorHelper {
         final Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
         retrofitBuilder.baseUrl(url);
         retrofitBuilder.client(client);
+        retrofitBuilder.addConverterFactory(GsonConverterFactory.create());
         final Retrofit retrofit = retrofitBuilder.build();
         return retrofit.create(WebHookService.class);
     }
+
+    @SuppressWarnings("rawtypes")
+    public Callback createCallback(final String url, String payload) {
+
+        return new Callback() {
+
+            @Override
+            public void onResponse(@SuppressWarnings("unused") Call call, 
retrofit2.Response response) {
+                LOG.info("URL: {} - Status: {}", url, response.code());
+            }
+
+            @Override
+            public void onFailure(@SuppressWarnings("unused") Call call, 
Throwable t) {
+                LOG.error("URL: {} - Retrofit failure occured", url, t);
+            }
+        };
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
 
b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
index b2ab509..449ec23 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java
@@ -39,6 +39,7 @@ import javax.persistence.Table;
 import javax.persistence.Temporal;
 import javax.persistence.TemporalType;
 import javax.persistence.UniqueConstraint;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import 
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@@ -400,6 +401,17 @@ public class AppUser extends AbstractPersistableCustom 
implements PlatformUser {
         return this.username;
     }
 
+    public String getDisplayName() {
+        if (this.staff != null && 
StringUtils.isNotBlank(this.staff.displayName())) {
+            return this.staff.displayName();
+        }
+        String firstName = StringUtils.isNotBlank(this.firstname) ? 
this.firstname : "";
+        if (StringUtils.isNotBlank(this.lastname)) {
+            return firstName + " " + this.lastname;
+        }
+        return firstName;
+    }
+
     @Override
     public boolean isAccountNonExpired() {
         return this.accountNonExpired;
diff --git 
a/fineract-provider/src/main/resources/sql/migrations/core_db/V364__elastic_hook_template.sql
 
b/fineract-provider/src/main/resources/sql/migrations/core_db/V364__elastic_hook_template.sql
new file mode 100644
index 0000000..1e158ec
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/sql/migrations/core_db/V364__elastic_hook_template.sql
@@ -0,0 +1,33 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+
+INSERT INTO m_hook_templates
+( name)
+VALUES('Elastic Search');
+
+INSERT INTO m_hook_schema
+(hook_template_id, field_type, field_name, placeholder, optional)
+VALUES( (select id from m_hook_templates mht  where name = 'Elastic Search'), 
'string', 'Payload URL', 'http://<host>/<index name>/<type name>', 0);
+INSERT INTO m_hook_schema
+( hook_template_id, field_type, field_name, placeholder, optional)
+VALUES( (select id from m_hook_templates mht  where name = 'Elastic Search'), 
'string', 'Content Type', 'json', 0);
+INSERT INTO m_hook_schema
+( hook_template_id, field_type, field_name, placeholder, optional)
+VALUES( (select id from m_hook_templates mht  where name = 'Elastic Search'), 
'string', 'Index Name', NULL, 1);

Reply via email to