@@ -1991,6 +1991,22 @@
|
||||
Global Configuration</a></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#configs_hooks">Hooks</a></td>
|
||||
<td>hooks</td>
|
||||
<td><a href="#configs_hooks_create">Create a Hook</a></td>
|
||||
<td><a href="#configs_hooks_retrieve">List Hooks</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>hooks/{hookId}</td>
|
||||
<td></td>
|
||||
<td><a href="#configs_hook_retrieve">Retrieve a Hook</a></td>
|
||||
<td><a href="#configs_hook_update">Update a Hook</a></td>
|
||||
<td><a href="#configs_hook_delete">Delete a Hook</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#configs_codes">Codes</a></td>
|
||||
<td>codes</td>
|
||||
@@ -2446,6 +2462,10 @@
|
||||
<td>users/template</td>
|
||||
<td><a href="#users_template">Retrieve User Template</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>hooks/template</td>
|
||||
<td><a href="#hooks_template">Retrieve Hooks Template</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>audits/searchtemplate</td>
|
||||
<td><a href="#audits_searchtemplate">Retrieve Audit Search Template</a></td>
|
||||
@@ -13335,6 +13355,300 @@ Request Body:
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<a id="configs_hooks" name="configs_hooks" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h3>Hooks</h3>
|
||||
<p>Hooks are a mechanism to trigger custom code on the occurence of events.</p>
|
||||
<p>Each template during hook creation represents custom behaviour on what actions should be taken on the triggering of a registered event for a hook. The action taken might be firing a HTTP request to another server or execute internal code.
|
||||
<p />
|
||||
<p>In order for MifosX to send webhook payloads, your server needs to be accessible from the Internet. MifosX will send along a few HTTP headers to differentiate between event types.</p>
|
||||
<p>X-Mifos-Entity - The entity type that was triggered. <br/>
|
||||
X-Mifos-Action - The action triggered on the entity type. <br/>
|
||||
X-Mifos-Platform-TenantId - The tenant which experienced the event.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="configs_hooks_create" name="configs_hooks_create"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Create a Hook</h4>
|
||||
<p>The following parameters can be passed for the creation of a hook :-
|
||||
<ul>
|
||||
<li> name - string - Required. The name of the template that is being called. (See /hooks/template for the list of valid hook names.)</li>
|
||||
<li> isActive - boolean - Determines whether the hook is actually triggered.</li>
|
||||
<li> events - array - Determines what events the hook is triggered for.
|
||||
</li>
|
||||
<li> config - hash - Required. Key/value pairs to provide settings for this hook. These settings vary between the templates.</li>
|
||||
<ul></p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
POST https://DomainName/api/v1/hooks
|
||||
</code>
|
||||
<code class="method-request">
|
||||
POST hooks
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"name": "Web",
|
||||
"isActive": true,
|
||||
"displayName": "Kremlin",
|
||||
"events": [
|
||||
{
|
||||
"actionName": "DISBURSE",
|
||||
"entityName": "LOAN"
|
||||
},
|
||||
{
|
||||
"actionName": "REPAYMENT",
|
||||
"entityName": "LOAN"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"Payload URL": "http://example.com/webhook",
|
||||
"Content Type": "json"
|
||||
}
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 4
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="configs_hooks_retrieve" name="configs_hooks_retrieve"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Retrieve Hooks</h4>
|
||||
<p>Returns the list of hooks.</p>
|
||||
<p>Example Requests:</p>
|
||||
<div class=apiClick>hooks</div>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
GET https://DomainName/api/v1/hooks
|
||||
</code>
|
||||
<code class="method-response">
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Web",
|
||||
"displayName": "Kremlin",
|
||||
"isActive": true,
|
||||
"createdAt": [2014, 9, 16],
|
||||
"updatedAt": [2014, 9, 16],
|
||||
"events": [
|
||||
{
|
||||
"actionName": "DISBURSE",
|
||||
"entityName": "LOAN"
|
||||
},
|
||||
{
|
||||
"actionName": "REPAYMENT",
|
||||
"entityName": "LOAN"
|
||||
}
|
||||
],
|
||||
"config": [
|
||||
{
|
||||
"fieldName": "Content Type",
|
||||
"fieldValue": "json"
|
||||
},
|
||||
{
|
||||
"fieldName": "Payload URL",
|
||||
"fieldValue": "https://abc.com/api"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="configs_hook_retrieve" name="configs_hook_retrieve"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Retrieve a Hook</h4>
|
||||
<p>Returns the details of a Hook.</p>
|
||||
<p>Example Requests:</p>
|
||||
<div class=apiClick>hooks/1</div>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
GET https://DomainName/api/v1/hooks/{hookId}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Web",
|
||||
"displayName": "Kremlin",
|
||||
"isActive": true,
|
||||
"createdAt": [2014, 9, 16],
|
||||
"updatedAt": [2014, 9, 16],
|
||||
"events": [
|
||||
{
|
||||
"actionName": "DISBURSE",
|
||||
"entityName": "LOAN"
|
||||
},
|
||||
{
|
||||
"actionName": "REPAYMENT",
|
||||
"entityName": "LOAN"
|
||||
}
|
||||
],
|
||||
"config": [
|
||||
{
|
||||
"fieldName": "Content Type",
|
||||
"fieldValue": "json"
|
||||
},
|
||||
{
|
||||
"fieldName": "Payload URL",
|
||||
"fieldValue": "https://abc.com/api"
|
||||
}
|
||||
]
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="configs_hook_update" name="configs_hook_update"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Update a Hook</h4>
|
||||
<p>Updates the details of a hook.</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
PUT https://DomainName/api/v1/hooks/{hookId}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
PUT hooks/4
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"name": "Web",
|
||||
"isActive": true,
|
||||
"displayName": "Local SMS Provider",
|
||||
"events": [
|
||||
{
|
||||
"actionName": "DISBURSE",
|
||||
"entityName": "LOAN"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"Payload URL": "http://changed.com/sms",
|
||||
"Content Type": "json"
|
||||
}
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 4,
|
||||
"changes": {
|
||||
"displayName": "Local SMS Provider",
|
||||
"events": [
|
||||
{
|
||||
"actionName": "DISBURSE",
|
||||
"entityName": "LOAN"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"Payload URL": "http://changed.com/sms",
|
||||
"Content Type": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="configs_hook_delete" name="configs_hook_delete"
|
||||
class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Delete a Hook</h4>
|
||||
<p>Deletes a hook.</p>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
DELETE https://DomainName/api/v1/hooks/{hookId}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
DELETE hooks/4
|
||||
Content-Type: application/json
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 4
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="hooks_template" name="hooks_template" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
<div class="method-description">
|
||||
<h4>Retrieve Hooks Template</h4>
|
||||
<p>This is a convenience resource. It can be useful when
|
||||
building maintenance user interface screens for client
|
||||
applications. The template data returned consists of any or all
|
||||
of:
|
||||
<ul>
|
||||
<li class=normalli>Field Defaults</li>
|
||||
<li class=normalli>Allowed Value Lists</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>Example Request:</p>
|
||||
<div class=apiClick>hooks/template</div>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
GET https://DomainName/api/v1/hooks/template
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"name": "Web",
|
||||
"schema": [
|
||||
{
|
||||
"fieldName": "Content Type",
|
||||
"fieldType": "string",
|
||||
"optional": true,
|
||||
"placeholder": "json / form"
|
||||
},
|
||||
{
|
||||
"fieldName": "Payload URL",
|
||||
"fieldType": "string",
|
||||
"optional": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
},...
|
||||
],
|
||||
"groupings": [
|
||||
{
|
||||
"name": "jobs",
|
||||
"entities": [
|
||||
{
|
||||
"name": "SCHEDULER",
|
||||
"actions": ["EXECUTEJOB", "UPDATE"]
|
||||
},
|
||||
{
|
||||
},...
|
||||
]
|
||||
},
|
||||
{
|
||||
},...
|
||||
]
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a id="configs_codes" name="configs_codes" class="old-syle-anchor"> </a>
|
||||
<div class="method-section">
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
-- -----------------------------------------------------
|
||||
-- Table `m_hook_templates`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `m_hook_templates` (
|
||||
`id` SMALLINT(6) NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`id`))
|
||||
ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `m_hook`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `m_hook` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`template_id` SMALLINT(6) NOT NULL,
|
||||
`is_active` SMALLINT(3) NOT NULL DEFAULT 1,
|
||||
`name` VARCHAR(45) NOT NULL,
|
||||
`createdby_id` bigint(20) DEFAULT NULL,
|
||||
`created_date` datetime DEFAULT NULL,
|
||||
`lastmodifiedby_id` bigint(20) DEFAULT NULL,
|
||||
`lastmodified_date` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `fk_template_id_idx` (`template_id` ASC),
|
||||
CONSTRAINT `fk_template_id`
|
||||
FOREIGN KEY (`template_id`)
|
||||
REFERENCES `m_hook_templates` (`id`))
|
||||
ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `m_hook_schema`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `m_hook_schema` (
|
||||
`id` SMALLINT(6) NOT NULL AUTO_INCREMENT,
|
||||
`hook_template_id` SMALLINT(6) NOT NULL,
|
||||
`field_type` VARCHAR(45) NOT NULL,
|
||||
`field_name` VARCHAR(100) NOT NULL,
|
||||
`placeholder` VARCHAR(100) DEFAULT NULL,
|
||||
`optional` TINYINT(3) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `fk_hook_template_id_idx` (`hook_template_id` ASC),
|
||||
CONSTRAINT `fk_hook_template_id`
|
||||
FOREIGN KEY (`hook_template_id`)
|
||||
REFERENCES `m_hook_templates` (`id`))
|
||||
ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `m_hook_registered_events`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `m_hook_registered_events` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`hook_id` BIGINT(20) NOT NULL,
|
||||
`entity_name` VARCHAR(45) NOT NULL,
|
||||
`action_name` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `fk_hook_id_idx` (`hook_id` ASC),
|
||||
CONSTRAINT `fk_hook_idc`
|
||||
FOREIGN KEY (`hook_id`)
|
||||
REFERENCES `m_hook` (`id`))
|
||||
ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `m_hook_configuration`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `m_hook_configuration` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`hook_id` BIGINT(20) NULL,
|
||||
`field_type` VARCHAR(45) NOT NULL,
|
||||
`field_name` VARCHAR(100) NOT NULL,
|
||||
`field_value` VARCHAR(100) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `fk_hook_id_idx` (`hook_id` ASC),
|
||||
CONSTRAINT `fk_hook_id_cfg`
|
||||
FOREIGN KEY (`hook_id`)
|
||||
REFERENCES `m_hook` (`id`))
|
||||
ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES ('configuration', 'CREATE_HOOK', 'HOOK', 'CREATE', 0);
|
||||
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES ('configuration', 'READ_HOOK', 'HOOK', 'READ', 0);
|
||||
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES ('configuration', 'UPDATE_HOOK', 'HOOK', 'UPDATE', 0);
|
||||
insert into `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||
VALUES ('configuration', 'DELETE_HOOK', 'HOOK', 'DELETE', 0);
|
||||
|
||||
insert into m_hook_templates values(1, "Web");
|
||||
insert into m_hook_templates values(2, "SMS Bridge");
|
||||
|
||||
INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
|
||||
VALUES (1, 'string', 'Payload URL', 0);
|
||||
INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `placeholder`, `optional`)
|
||||
VALUES (1, 'string', 'Content Type', 'json / form', 0);
|
||||
INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
|
||||
VALUES (2, 'string', 'Payload URL', 0);
|
||||
INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
|
||||
VALUES (2, 'string', 'SMS Provider', 0);
|
||||
INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
|
||||
VALUES (2, 'string', 'Phone Number', 0);
|
||||
INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
|
||||
VALUES (2, 'string', 'SMS Provider Token', 0);
|
||||
INSERT INTO `m_hook_schema` (`hook_template_id`, `field_type`, `field_name`, `optional`)
|
||||
VALUES (2, 'string', 'SMS Provider Account Id', 0);
|
||||
|
||||
|
||||
@@ -90,6 +90,11 @@ dependencies {
|
||||
[group: 'com.sun.jersey.contribs', name: 'jersey-spring', version: jerseyVersion],
|
||||
[group: 'com.sun.jersey.contribs', name: 'jersey-multipart', version: jerseyVersion],
|
||||
|
||||
[group: 'com.squareup.retrofit', name: 'retrofit', version: '1.6.1'],
|
||||
[group: 'com.squareup.okhttp', name: 'okhttp', version: '2.0.0'],
|
||||
[group: 'com.squareup.okhttp', name: 'okhttp-urlconnection', version: '2.0.0'],
|
||||
|
||||
|
||||
[group: 'com.google.code.gson', name: 'gson', version: '2.2.4'],
|
||||
[group: 'com.google.guava', name: 'guava', version: '15.0'],
|
||||
|
||||
|
||||
@@ -233,6 +233,10 @@ public class CommandWrapper {
|
||||
return this.entityName.equalsIgnoreCase("USER");
|
||||
}
|
||||
|
||||
public boolean isHookResource() {
|
||||
return this.entityName.equalsIgnoreCase("HOOK");
|
||||
}
|
||||
|
||||
public boolean isCurrencyResource() {
|
||||
return this.entityName.equalsIgnoreCase("CURRENCY");
|
||||
}
|
||||
|
||||
+24
@@ -302,6 +302,30 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createHook() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "HOOK";
|
||||
this.entityId = null;
|
||||
this.href = "/hooks/template";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateHook(final Long hookId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "HOOK";
|
||||
this.entityId = hookId;
|
||||
this.href = "/hooks/" + hookId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder deleteHook(final Long hookId) {
|
||||
this.actionName = "DELETE";
|
||||
this.entityName = "HOOK";
|
||||
this.entityId = hookId;
|
||||
this.href = "/hooks/" + hookId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createCharge() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "CHARGE";
|
||||
|
||||
+38
-2
@@ -19,6 +19,9 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.mifosplatform.infrastructure.hooks.event.HookEvent;
|
||||
import org.mifosplatform.infrastructure.hooks.event.HookEventSource;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -32,17 +35,21 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
private PlatformSecurityContext context;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
|
||||
private final ToApiJsonSerializer<CommandProcessingResult> toApiResultJsonSerializer;
|
||||
private CommandSourceRepository commandSourceRepository;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
|
||||
@Autowired
|
||||
public SynchronousCommandProcessingService(final PlatformSecurityContext context, final ApplicationContext applicationContext,
|
||||
final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer, final CommandSourceRepository commandSourceRepository,
|
||||
final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer,
|
||||
final ToApiJsonSerializer<CommandProcessingResult> toApiResultJsonSerializer,
|
||||
final CommandSourceRepository commandSourceRepository,
|
||||
final ConfigurationDomainService configurationDomainService) {
|
||||
this.context = context;
|
||||
this.context = context;
|
||||
this.applicationContext = applicationContext;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
this.toApiResultJsonSerializer = toApiResultJsonSerializer;
|
||||
this.commandSourceRepository = commandSourceRepository;
|
||||
this.commandSourceRepository = commandSourceRepository;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
@@ -52,7 +59,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
@Override
|
||||
public CommandProcessingResult processAndLogCommand(final CommandWrapper wrapper, final JsonCommand command,
|
||||
final boolean isApprovedByChecker) {
|
||||
|
||||
|
||||
final boolean rollbackTransaction = this.configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
|
||||
|
||||
final NewCommandSourceHandler handler = findCommandHandler(wrapper);
|
||||
@@ -101,6 +108,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSourceResult);
|
||||
}
|
||||
result.setRollbackTransaction(null);
|
||||
|
||||
publishEvent(wrapper.entityName(), wrapper.actionName(), result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -223,6 +233,16 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
} else {
|
||||
throw new UnsupportedCommandException(wrapper.commandName());
|
||||
}
|
||||
} else if (wrapper.isHookResource()) {
|
||||
if (wrapper.isCreate()) {
|
||||
handler = this.applicationContext.getBean("createHookCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isUpdate()) {
|
||||
handler = this.applicationContext.getBean("updateHookCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isDelete()) {
|
||||
handler = this.applicationContext.getBean("deleteHookCommandHandler", NewCommandSourceHandler.class);
|
||||
} else {
|
||||
throw new UnsupportedCommandException(wrapper.commandName());
|
||||
}
|
||||
} else if (wrapper.isStaffResource()) {
|
||||
if (wrapper.isCreate()) {
|
||||
handler = this.applicationContext.getBean("createStaffCommandHandler", NewCommandSourceHandler.class);
|
||||
@@ -851,4 +871,20 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
user.validateHasPermissionTo(commandWrapper.getTaskPermissionName());
|
||||
return rollbackTransaction;
|
||||
}
|
||||
|
||||
private void publishEvent(final String entityName, final String actionName, final CommandProcessingResult result) {
|
||||
|
||||
final String authToken = ThreadLocalContextUtil.getAuthToken();
|
||||
final String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
|
||||
final AppUser appUser = this.context.authenticatedUser();
|
||||
|
||||
final HookEventSource hookEventSource = new HookEventSource(entityName, actionName);
|
||||
|
||||
final String serializedResult = this.toApiResultJsonSerializer.serialize(result);
|
||||
|
||||
final HookEvent applicationEvent =
|
||||
new HookEvent(hookEventSource, serializedResult, tenantIdentifier, appUser, authToken);
|
||||
|
||||
applicationContext.publishEvent(applicationEvent);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.core.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -12,7 +13,7 @@ import java.util.Map;
|
||||
* Represents the successful result of an REST API call that results in
|
||||
* processing a command.
|
||||
*/
|
||||
public class CommandProcessingResult {
|
||||
public class CommandProcessingResult implements Serializable {
|
||||
|
||||
private Long commandId;
|
||||
private Long officeId;
|
||||
|
||||
+10
@@ -18,6 +18,8 @@ public class ThreadLocalContextUtil {
|
||||
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
|
||||
|
||||
private static final ThreadLocal<MifosPlatformTenant> tenantcontext = new ThreadLocal<>();
|
||||
|
||||
private static final ThreadLocal<String> authTokenContext = new ThreadLocal<>();
|
||||
|
||||
public static void setTenant(final MifosPlatformTenant tenant) {
|
||||
Assert.notNull(tenant, "tenant cannot be null");
|
||||
@@ -43,5 +45,13 @@ public class ThreadLocalContextUtil {
|
||||
public static void clearDataSourceContext() {
|
||||
contextHolder.remove();
|
||||
}
|
||||
|
||||
public static void setAuthToken(final String authToken) {
|
||||
authTokenContext.set(authToken);
|
||||
}
|
||||
|
||||
public static String getAuthToken() {
|
||||
return authTokenContext.get();
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.api;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class HookApiConstants {
|
||||
|
||||
public static final String HOOK_RESOURCE_NAME = "HOOK";
|
||||
|
||||
public static final String nameParamName = "name";
|
||||
|
||||
public static final String displayNameParamName = "displayName";
|
||||
|
||||
public static final String isActiveParamName = "isActive";
|
||||
|
||||
public static final String webTemplateName = "Web";
|
||||
|
||||
public static final String smsTemplateName = "SMS Bridge";
|
||||
|
||||
public static final String payloadURLName = "Payload URL";
|
||||
|
||||
public static final String contentTypeName = "Content Type";
|
||||
|
||||
public static final String smsProviderName = "SMS Provider";
|
||||
|
||||
public static final String smsProviderAccountIdName = "SMS Provider Account Id";
|
||||
|
||||
public static final String smsProviderTokenIdName = "SMS Provider Token";
|
||||
|
||||
public static final String phoneNumberName = "Phone Number";
|
||||
|
||||
public static final String apiKeyName = "Api Key";
|
||||
|
||||
public static final String configParamName = "config";
|
||||
|
||||
public static final String eventsParamName = "events";
|
||||
|
||||
public static final String entityNameParamName = "entityName";
|
||||
|
||||
public static final String actionNameParamName = "actionName";
|
||||
|
||||
public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(nameParamName, displayNameParamName,
|
||||
isActiveParamName, configParamName, eventsParamName));
|
||||
|
||||
public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(nameParamName, displayNameParamName,
|
||||
isActiveParamName, configParamName, eventsParamName));
|
||||
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.api;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.HOOK_RESOURCE_NAME;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.RESPONSE_DATA_PARAMETERS;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.mifosplatform.commands.domain.CommandWrapper;
|
||||
import org.mifosplatform.commands.service.CommandWrapperBuilder;
|
||||
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.mifosplatform.infrastructure.hooks.data.HookData;
|
||||
import org.mifosplatform.infrastructure.hooks.service.HookReadPlatformService;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/hooks")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
public class HookApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final HookReadPlatformService readPlatformService;
|
||||
private final DefaultToApiJsonSerializer<HookData> toApiJsonSerializer;
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public HookApiResource(
|
||||
final PlatformSecurityContext context,
|
||||
final HookReadPlatformService readPlatformService,
|
||||
final DefaultToApiJsonSerializer<HookData> toApiJsonSerializer,
|
||||
final ApiRequestParameterHelper apiRequestParameterHelper,
|
||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
|
||||
this.context = context;
|
||||
this.readPlatformService = readPlatformService;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
||||
}
|
||||
|
||||
@GET
|
||||
public String retrieveHooks(@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(
|
||||
HOOK_RESOURCE_NAME);
|
||||
|
||||
final Collection<HookData> hooks = this.readPlatformService
|
||||
.retrieveAllHooks();
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
|
||||
.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, hooks,
|
||||
RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{hookId}")
|
||||
public String retrieveHook(@PathParam("hookId") final Long hookId,
|
||||
@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(
|
||||
HOOK_RESOURCE_NAME);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
|
||||
.process(uriInfo.getQueryParameters());
|
||||
|
||||
HookData hook = this.readPlatformService.retrieveHook(hookId);
|
||||
|
||||
if (settings.isTemplate()) {
|
||||
final HookData hookData = this.readPlatformService
|
||||
.retrieveNewHookDetails(hook.getTemplateName());
|
||||
hook = HookData.templateExisting(hook, hookData.getTemplates(),
|
||||
hookData.getGroupings());
|
||||
}
|
||||
return this.toApiJsonSerializer.serialize(settings, hook,
|
||||
RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
public String template(@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(
|
||||
HOOK_RESOURCE_NAME);
|
||||
|
||||
final HookData hook = this.readPlatformService
|
||||
.retrieveNewHookDetails(null);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
|
||||
.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, hook,
|
||||
RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
@POST
|
||||
public String createHook(final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.createHook().withJson(apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService
|
||||
.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{hookId}")
|
||||
public String updateHook(@PathParam("hookId") final Long hookId,
|
||||
final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.updateHook(hookId).withJson(apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService
|
||||
.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{hookId}")
|
||||
public String deleteHook(@PathParam("hookId") final Long hookId) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.deleteHook(hookId).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService
|
||||
.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Entity implements Serializable {
|
||||
|
||||
private String name;
|
||||
|
||||
private List<String> actions;
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setActions(final List<String> actions) {
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Event implements Serializable {
|
||||
|
||||
private final String actionName;
|
||||
private final String entityName;
|
||||
|
||||
public static Event instance(final String actionName,
|
||||
final String entityName) {
|
||||
return new Event(actionName, entityName);
|
||||
}
|
||||
|
||||
private Event(final String actionName, final String entityName) {
|
||||
this.actionName = actionName;
|
||||
this.entityName = entityName;
|
||||
}
|
||||
|
||||
public String getActionName() {
|
||||
return this.actionName;
|
||||
}
|
||||
|
||||
public String getEntityName() {
|
||||
return this.entityName;
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.data;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
|
||||
public class EventResultSetExtractor implements
|
||||
ResultSetExtractor<List<Grouping>> {
|
||||
|
||||
@Override
|
||||
public List<Grouping> extractData(final ResultSet rs) throws SQLException,
|
||||
DataAccessException {
|
||||
final List<Grouping> groupings = new ArrayList<>();
|
||||
|
||||
final Map<String, Map<String, List<String>>> groupToEntityMapping = new HashMap<>();
|
||||
Map<String, List<String>> entityToActionMapping = new HashMap<>();
|
||||
|
||||
while (rs.next()) {
|
||||
final String groupingName = rs.getString("grouping");
|
||||
final String entityName = rs.getString("entity_name");
|
||||
final String actionName = rs.getString("action_name");
|
||||
Map<String, List<String>> entities = groupToEntityMapping
|
||||
.get(groupingName);
|
||||
List<String> actions = entityToActionMapping.get(entityName);
|
||||
|
||||
if (entities == null) {
|
||||
entityToActionMapping = new HashMap<>();
|
||||
}
|
||||
|
||||
if (actions == null) {
|
||||
actions = new ArrayList<>();
|
||||
}
|
||||
actions.add(actionName);
|
||||
entityToActionMapping.put(entityName, actions);
|
||||
|
||||
if (entities == null) {
|
||||
entities = new HashMap<>();
|
||||
}
|
||||
entities.putAll(entityToActionMapping);
|
||||
groupToEntityMapping.put(groupingName, entities);
|
||||
}
|
||||
|
||||
for (final Entry<String, Map<String, List<String>>> groupingEntry : groupToEntityMapping
|
||||
.entrySet()) {
|
||||
final List<Entity> entities = new ArrayList<>();
|
||||
final Grouping group = new Grouping();
|
||||
group.setName(groupingEntry.getKey());
|
||||
for (final Entry<String, List<String>> entityEntry : groupingEntry
|
||||
.getValue().entrySet()) {
|
||||
final List<String> actions = new ArrayList<>();
|
||||
final Entity entity = new Entity();
|
||||
entity.setName(entityEntry.getKey());
|
||||
for (final String action : entityEntry.getValue()) {
|
||||
actions.add(action);
|
||||
}
|
||||
Collections.sort(actions);
|
||||
entity.setActions(actions);
|
||||
entities.add(entity);
|
||||
}
|
||||
|
||||
Collections.sort(entities, new Comparator<Entity>() {
|
||||
@Override
|
||||
public int compare(final Entity entity1, final Entity entity2) {
|
||||
return entity1.getName().compareTo(entity2.getName());
|
||||
}
|
||||
});
|
||||
group.setEntities(entities);
|
||||
groupings.add(group);
|
||||
}
|
||||
|
||||
Collections.sort(groupings, new Comparator<Grouping>() {
|
||||
@Override
|
||||
public int compare(final Grouping grouping1,
|
||||
final Grouping grouping2) {
|
||||
return grouping1.getName().compareTo(grouping2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
return groupings;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.data;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Field {
|
||||
|
||||
private final String fieldName;
|
||||
private final String fieldValue;
|
||||
private final String fieldType;
|
||||
private final Boolean optional;
|
||||
private final String placeholder;
|
||||
|
||||
public static Field fromConfig(final String fieldName,
|
||||
final String fieldValue) {
|
||||
return new Field(null, fieldName, fieldValue, null, null);
|
||||
}
|
||||
|
||||
public static Field fromSchema(final String fieldType,
|
||||
final String fieldName, final Boolean optional,
|
||||
final String placeholder) {
|
||||
return new Field(fieldType, fieldName, null, optional, placeholder);
|
||||
}
|
||||
|
||||
private Field(final String fieldType, final String fieldName,
|
||||
final String fieldValue, final Boolean optional,
|
||||
final String placeholder) {
|
||||
this.fieldType = fieldType;
|
||||
this.fieldName = fieldName;
|
||||
this.fieldValue = fieldValue;
|
||||
this.optional = optional;
|
||||
this.placeholder = placeholder;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return this.fieldName;
|
||||
}
|
||||
|
||||
public String getFieldValue() {
|
||||
return this.fieldValue;
|
||||
}
|
||||
|
||||
public String getFieldType() {
|
||||
return this.fieldType;
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Grouping implements Serializable {
|
||||
|
||||
private String name;
|
||||
|
||||
private List<Entity> entities;
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setEntities(final List<Entity> entities) {
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
public class HookData implements Serializable {
|
||||
|
||||
private final Long id;
|
||||
private final String name;
|
||||
private final String displayName;
|
||||
private final Boolean isActive;
|
||||
private final LocalDate createdAt;
|
||||
private final LocalDate updatedAt;
|
||||
|
||||
// associations
|
||||
private final List<Event> events;
|
||||
private final List<Field> config;
|
||||
|
||||
// template data
|
||||
private final List<HookTemplateData> templates;
|
||||
private final List<Grouping> groupings;
|
||||
|
||||
public static HookData instance(final Long id, final String name,
|
||||
final String displayName, final boolean isActive,
|
||||
final LocalDate createdAt, final LocalDate updatedAt,
|
||||
final List<Event> registeredEvents, final List<Field> config) {
|
||||
return new HookData(id, name, displayName, isActive, createdAt,
|
||||
updatedAt, registeredEvents, config, null, null);
|
||||
}
|
||||
|
||||
public static HookData template(final List<HookTemplateData> templates,
|
||||
final List<Grouping> groupings) {
|
||||
return new HookData(null, null, null, null, null, null, null, null,
|
||||
templates, groupings);
|
||||
}
|
||||
|
||||
public static HookData templateExisting(final HookData hookData,
|
||||
final List<HookTemplateData> templates,
|
||||
final List<Grouping> groupings) {
|
||||
return new HookData(hookData.id, hookData.name, hookData.displayName,
|
||||
hookData.isActive, hookData.createdAt, hookData.updatedAt,
|
||||
hookData.events, hookData.config, templates, groupings);
|
||||
}
|
||||
|
||||
private HookData(final Long id, final String name,
|
||||
final String displayName, final Boolean isActive,
|
||||
final LocalDate createdAt, final LocalDate updatedAt,
|
||||
final List<Event> events, final List<Field> config,
|
||||
final List<HookTemplateData> templates,
|
||||
final List<Grouping> groupings) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.isActive = isActive;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
|
||||
// associations
|
||||
this.events = events;
|
||||
this.config = config;
|
||||
|
||||
// template
|
||||
this.templates = templates;
|
||||
this.groupings = groupings;
|
||||
|
||||
}
|
||||
|
||||
public Long getHookId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public List<HookTemplateData> getTemplates() {
|
||||
return this.templates;
|
||||
}
|
||||
|
||||
public List<Grouping> getGroupings() {
|
||||
return this.groupings;
|
||||
}
|
||||
|
||||
public String getTemplateName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HookTemplateData implements Serializable {
|
||||
|
||||
private final Long id;
|
||||
private final String name;
|
||||
|
||||
// associations
|
||||
private final List<Field> schema;
|
||||
|
||||
public static HookTemplateData instance(final Long id, final String name,
|
||||
final List<Field> schema) {
|
||||
return new HookTemplateData(id, name, schema);
|
||||
}
|
||||
|
||||
private HookTemplateData(final Long id, final String name,
|
||||
final List<Field> schema) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
|
||||
// associations
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public Long getServiceId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.configParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.displayNameParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.eventsParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.isActiveParamName;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_hook")
|
||||
public class Hook extends AbstractAuditableCustom<AppUser, Long> {
|
||||
|
||||
@Column(name = "name", nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive;
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true)
|
||||
private Set<HookResource> events = new HashSet<>();
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true)
|
||||
private Set<HookConfiguration> config = new HashSet<>();
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
@JoinColumn(name = "template_id", referencedColumnName = "id", nullable = false)
|
||||
private HookTemplate template;
|
||||
|
||||
protected Hook() {
|
||||
//
|
||||
}
|
||||
|
||||
public static Hook fromJson(final JsonCommand command,
|
||||
final HookTemplate template, final Set<HookConfiguration> config,
|
||||
final Set<HookResource> events) {
|
||||
final String displayName = command
|
||||
.stringValueOfParameterNamed(displayNameParamName);
|
||||
final Boolean isActive = command
|
||||
.booleanObjectValueOfParameterNamed(isActiveParamName);
|
||||
return new Hook(template, displayName, isActive, config, events);
|
||||
}
|
||||
|
||||
private Hook(final HookTemplate template, final String displayName,
|
||||
final Boolean isActive, final Set<HookConfiguration> config,
|
||||
final Set<HookResource> events) {
|
||||
|
||||
this.template = template;
|
||||
|
||||
if (StringUtils.isNotBlank(displayName)) {
|
||||
this.name = displayName.trim();
|
||||
} else {
|
||||
this.name = template.getName();
|
||||
}
|
||||
this.isActive = isActive;
|
||||
if (!CollectionUtils.isEmpty(config)) {
|
||||
this.config = associateConfigWithThisHook(config);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(events)) {
|
||||
this.events = associateEventsWithThisHook(events);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Set<HookConfiguration> associateConfigWithThisHook(
|
||||
final Set<HookConfiguration> config) {
|
||||
for (final HookConfiguration hookConfiguration : config) {
|
||||
hookConfiguration.update(this);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private Set<HookResource> associateEventsWithThisHook(
|
||||
final Set<HookResource> events) {
|
||||
for (final HookResource hookResource : events) {
|
||||
hookResource.update(this);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public HookTemplate getHookTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
public Set<HookConfiguration> getHookConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
public Map<String, Object> update(final JsonCommand command) {
|
||||
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(4);
|
||||
|
||||
if (command.isChangeInStringParameterNamed(displayNameParamName,
|
||||
this.name)) {
|
||||
final String newValue = command
|
||||
.stringValueOfParameterNamed(displayNameParamName);
|
||||
actualChanges.put(displayNameParamName, newValue);
|
||||
this.name = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInBooleanParameterNamed(isActiveParamName,
|
||||
this.isActive)) {
|
||||
final Boolean newValue = command
|
||||
.booleanObjectValueOfParameterNamed(isActiveParamName);
|
||||
actualChanges.put(isActiveParamName, newValue);
|
||||
this.isActive = newValue;
|
||||
}
|
||||
|
||||
// events
|
||||
if (command.hasParameter(eventsParamName)) {
|
||||
final JsonArray jsonArray = command
|
||||
.arrayOfParameterNamed(eventsParamName);
|
||||
if (jsonArray != null) {
|
||||
actualChanges.put(eventsParamName, jsonArray);
|
||||
}
|
||||
}
|
||||
|
||||
// config
|
||||
if (command.hasParameter(configParamName)) {
|
||||
final JsonElement element = command.parsedJson().getAsJsonObject()
|
||||
.get(configParamName);
|
||||
if (element != null) {
|
||||
actualChanges.put(configParamName, element);
|
||||
}
|
||||
}
|
||||
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
public boolean updateEvents(final Set<HookResource> newHookEvents) {
|
||||
if (newHookEvents == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.events == null) {
|
||||
this.events = new HashSet<>();
|
||||
}
|
||||
this.events.clear();
|
||||
this.events.addAll(associateEventsWithThisHook(newHookEvents));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean updateConfig(final Set<HookConfiguration> newHookConfig) {
|
||||
if (newHookConfig == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.config == null) {
|
||||
this.config = new HashSet<>();
|
||||
}
|
||||
this.config.clear();
|
||||
this.config.addAll(associateConfigWithThisHook(newHookConfig));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_hook_configuration")
|
||||
public class HookConfiguration extends AbstractPersistable<Long> {
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "hook_id", referencedColumnName = "id", nullable = false)
|
||||
private Hook hook;
|
||||
|
||||
@Column(name = "field_type", nullable = false, length = 20)
|
||||
private String fieldType;
|
||||
|
||||
@Column(name = "field_name", nullable = false, length = 100)
|
||||
private String fieldName;
|
||||
|
||||
@Column(name = "field_value", nullable = false, length = 100)
|
||||
private String fieldValue;
|
||||
|
||||
public static HookConfiguration createNewWithoutHook(
|
||||
final String fieldType, final String fieldName,
|
||||
final String fieldValue) {
|
||||
return new HookConfiguration(null, fieldType, fieldName, fieldValue);
|
||||
}
|
||||
|
||||
public static HookConfiguration createNew(final Hook hook, final String fieldType,
|
||||
final String fieldName, final String fieldValue) {
|
||||
return new HookConfiguration(hook, fieldType, fieldName, fieldValue);
|
||||
}
|
||||
|
||||
protected HookConfiguration() {
|
||||
//
|
||||
}
|
||||
|
||||
private HookConfiguration(final Hook hook, final String fieldType,
|
||||
final String fieldName, final String fieldValue) {
|
||||
this.hook = hook;
|
||||
this.fieldType = fieldType;
|
||||
this.fieldName = fieldName;
|
||||
this.fieldValue = fieldValue;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return this.fieldName;
|
||||
}
|
||||
|
||||
public String getFieldType() {
|
||||
return this.fieldType;
|
||||
}
|
||||
|
||||
public String getFieldValue() {
|
||||
return this.fieldValue;
|
||||
}
|
||||
|
||||
public void update(final Hook hook) {
|
||||
this.hook = hook;
|
||||
}
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface HookConfigurationRepository extends
|
||||
JpaRepository<HookConfiguration, Long>,
|
||||
JpaSpecificationExecutor<HookConfiguration> {
|
||||
|
||||
@Query("select config.fieldValue from HookConfiguration config where config.hook.id = :hookId and config.fieldName = :fieldName")
|
||||
String findOneByHookIdAndFieldName(@Param("hookId") Long hookId,
|
||||
@Param("fieldName") String fieldName);
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface HookRepository extends JpaRepository<Hook, Long>,
|
||||
JpaSpecificationExecutor<Hook> {
|
||||
|
||||
@Query("select hook from Hook hook inner join hook.events event where event.entityName = :entityName and event.actionName = :actionName and hook.isActive = 1")
|
||||
List<Hook> findAllHooksListeningToEvent(
|
||||
@Param("entityName") String entityName,
|
||||
@Param("actionName") String actionName);
|
||||
|
||||
@Query("from Hook hook where hook.template.id = :templateId ")
|
||||
Hook findOneByTemplateId(@Param("templateId") Long templateId);
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_hook_registered_events")
|
||||
public class HookResource extends AbstractPersistable<Long> {
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "hook_id", referencedColumnName = "id", nullable = false)
|
||||
private Hook hook;
|
||||
|
||||
@Column(name = "entity_name", nullable = false, length = 45)
|
||||
private String entityName;
|
||||
|
||||
@Column(name = "action_name", nullable = false, length = 45)
|
||||
private String actionName;
|
||||
|
||||
protected HookResource() {
|
||||
//
|
||||
}
|
||||
|
||||
public static HookResource createNewWithoutHook(final String entityName,
|
||||
final String actionName) {
|
||||
return new HookResource(null, entityName, actionName);
|
||||
}
|
||||
|
||||
private HookResource(final Hook hook, final String entityName,
|
||||
final String actionName) {
|
||||
this.hook = hook;
|
||||
this.entityName = entityName;
|
||||
this.actionName = actionName;
|
||||
}
|
||||
|
||||
public void update(final Hook hook) {
|
||||
this.hook = hook;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.nameParamName;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_hook_templates")
|
||||
public class HookTemplate extends AbstractPersistable<Long> {
|
||||
|
||||
@Column(name = "name", nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "template", orphanRemoval = true)
|
||||
private final Set<Schema> fields = new HashSet<>();
|
||||
|
||||
private HookTemplate(final String name) {
|
||||
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
this.name = name.trim();
|
||||
} else {
|
||||
this.name = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected HookTemplate() {
|
||||
|
||||
}
|
||||
|
||||
public static HookTemplate fromJson(final JsonCommand command) {
|
||||
final String name = command.stringValueOfParameterNamed(nameParamName);
|
||||
return new HookTemplate(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Set<Schema> getSchema() {
|
||||
return this.fields;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface HookTemplateRepository extends
|
||||
JpaRepository<HookTemplate, Long>,
|
||||
JpaSpecificationExecutor<HookTemplate> {
|
||||
|
||||
@Query("from HookTemplate template where template.name = :name")
|
||||
HookTemplate findOne(@Param("name") String name);
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_hook_schema")
|
||||
public class Schema extends AbstractPersistable<Long> {
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "hook_template_id", referencedColumnName = "id", nullable = false)
|
||||
private HookTemplate template;
|
||||
|
||||
@Column(name = "field_type", nullable = false, length = 20)
|
||||
private String fieldType;
|
||||
|
||||
@Column(name = "field_name", nullable = false, length = 100)
|
||||
private String fieldName;
|
||||
|
||||
@Column(name = "placeholder", length = 100)
|
||||
private String placeholder;
|
||||
|
||||
@Column(name = "optional", nullable = false)
|
||||
private final boolean optional = false;
|
||||
|
||||
public Schema() {
|
||||
//
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return this.fieldName;
|
||||
}
|
||||
|
||||
public String getFieldType() {
|
||||
return this.fieldType;
|
||||
}
|
||||
|
||||
public boolean isOptional() {
|
||||
return this.optional;
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.event;
|
||||
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class HookEvent extends ApplicationEvent {
|
||||
|
||||
private final String payload;
|
||||
|
||||
private final String tenantIdentifier;
|
||||
|
||||
private final AppUser appUser;
|
||||
|
||||
private final String authToken;
|
||||
|
||||
public HookEvent(final HookEventSource source, final String payload,
|
||||
final String tenantIdentifier, final AppUser appUser,
|
||||
final String authToken) {
|
||||
super(source);
|
||||
this.payload = payload;
|
||||
this.tenantIdentifier = tenantIdentifier;
|
||||
this.appUser = appUser;
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookEventSource getSource() {
|
||||
return (HookEventSource) super.source;
|
||||
}
|
||||
|
||||
public String getTenantIdentifier() {
|
||||
return this.tenantIdentifier;
|
||||
}
|
||||
|
||||
public AppUser getAppUser() {
|
||||
return this.appUser;
|
||||
}
|
||||
|
||||
public String getAuthToken() {
|
||||
return this.authToken;
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.event;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class HookEventSource implements Serializable {
|
||||
|
||||
private final String entityName;
|
||||
|
||||
private final String actionName;
|
||||
|
||||
public HookEventSource(final String entityName, final String actionName) {
|
||||
super();
|
||||
this.entityName = entityName;
|
||||
this.actionName = actionName;
|
||||
}
|
||||
|
||||
public String getEntityName() {
|
||||
return this.entityName;
|
||||
}
|
||||
|
||||
public String getActionName() {
|
||||
return this.actionName;
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
public class HookNotFoundException extends
|
||||
AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public HookNotFoundException(final String name) {
|
||||
super("error.msg.hook.not.found", "Hook with name `" + name
|
||||
+ "` does not exist", name);
|
||||
}
|
||||
|
||||
public HookNotFoundException(final Long hookId) {
|
||||
super("error.msg.hook.identifier.not.found", "Hook with identifier `"
|
||||
+ hookId + "` does not exist", hookId);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
public class HookTemplateNotFoundException extends
|
||||
AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public HookTemplateNotFoundException(final String name) {
|
||||
super("error.msg.template.not.found", "Template with name `" + name
|
||||
+ "` does not exist", name);
|
||||
}
|
||||
|
||||
public HookTemplateNotFoundException(final Long templateId) {
|
||||
super("error.msg.template.identifier.not.found",
|
||||
"Template with identifier `" + templateId + "` does not exist",
|
||||
templateId);
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.handler;
|
||||
|
||||
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.hooks.service.HookWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class CreateHookCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final HookWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateHookCommandHandler(
|
||||
final HookWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.createHook(command);
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.handler;
|
||||
|
||||
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.hooks.service.HookWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class DeleteHookCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final HookWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public DeleteHookCommandHandler(
|
||||
final HookWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.deleteHook(command.entityId());
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.handler;
|
||||
|
||||
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.hooks.service.HookWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class UpdateHookCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final HookWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateHookCommandHandler(
|
||||
final HookWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService
|
||||
.updateHook(command.entityId(), command);
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.listener;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.event.HookEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
public interface HookListener extends ApplicationListener<HookEvent> {
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.listener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
|
||||
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookRepository;
|
||||
import org.mifosplatform.infrastructure.hooks.event.HookEvent;
|
||||
import org.mifosplatform.infrastructure.hooks.event.HookEventSource;
|
||||
import org.mifosplatform.infrastructure.hooks.processor.HookProcessor;
|
||||
import org.mifosplatform.infrastructure.hooks.processor.HookProcessorProvider;
|
||||
import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MifosHookListener implements HookListener {
|
||||
|
||||
private final HookProcessorProvider hookProcessorProvider;
|
||||
private final HookRepository hookRepository;
|
||||
private final TenantDetailsService tenantDetailsService;
|
||||
|
||||
@Autowired
|
||||
public MifosHookListener(final HookProcessorProvider hookProcessorProvider,
|
||||
final HookRepository hookRepository,
|
||||
final TenantDetailsService tenantDetailsService) {
|
||||
this.hookRepository = hookRepository;
|
||||
this.hookProcessorProvider = hookProcessorProvider;
|
||||
this.tenantDetailsService = tenantDetailsService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(final HookEvent event) {
|
||||
|
||||
final String tenantIdentifier = event.getTenantIdentifier();
|
||||
final MifosPlatformTenant tenant = this.tenantDetailsService
|
||||
.loadTenantById(tenantIdentifier);
|
||||
ThreadLocalContextUtil.setTenant(tenant);
|
||||
|
||||
final AppUser appUser = event.getAppUser();
|
||||
final String authToken = event.getAuthToken();
|
||||
|
||||
final HookEventSource hookEventSource = event.getSource();
|
||||
final String entityName = hookEventSource.getEntityName();
|
||||
final String actionName = hookEventSource.getActionName();
|
||||
final String payload = event.getPayload();
|
||||
|
||||
final List<Hook> hooks = this.hookRepository
|
||||
.findAllHooksListeningToEvent(hookEventSource.getEntityName(),
|
||||
hookEventSource.getActionName());
|
||||
for (final Hook hook : hooks) {
|
||||
final HookProcessor processor = this.hookProcessorProvider
|
||||
.getProcessor(hook);
|
||||
processor.process(hook, appUser, payload, entityName, actionName,
|
||||
tenantIdentifier, authToken);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.processor;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
|
||||
public interface HookProcessor {
|
||||
|
||||
void process(Hook hook, AppUser appUser, String payload, String entityName,
|
||||
String actionName, String tenantIdentifier, String authToken);
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.processor;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.smsTemplateName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.webTemplateName;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class HookProcessorProvider implements ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(
|
||||
final ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public HookProcessor getProcessor(final Hook hook) {
|
||||
HookProcessor processor;
|
||||
final String templateName = hook.getHookTemplate().getName();
|
||||
if (templateName.equalsIgnoreCase(smsTemplateName)) {
|
||||
processor = this.applicationContext.getBean("twilioHookProcessor",
|
||||
TwilioHookProcessor.class);
|
||||
} else if (templateName.equals(webTemplateName)) {
|
||||
processor = this.applicationContext.getBean("webHookProcessor",
|
||||
WebHookProcessor.class);
|
||||
} else {
|
||||
processor = null;
|
||||
}
|
||||
return processor;
|
||||
}
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.processor;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import retrofit.Callback;
|
||||
import retrofit.RestAdapter;
|
||||
import retrofit.RetrofitError;
|
||||
import retrofit.client.OkClient;
|
||||
import retrofit.client.Response;
|
||||
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ProcessorHelper {
|
||||
|
||||
private final static Logger logger = LoggerFactory
|
||||
.getLogger(ProcessorHelper.class);
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public static OkHttpClient configureClient(final OkHttpClient client) {
|
||||
final TrustManager[] certs = new TrustManager[] { new X509TrustManager() {
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(final X509Certificate[] chain,
|
||||
final String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(final X509Certificate[] chain,
|
||||
final String authType) throws CertificateException {
|
||||
}
|
||||
} };
|
||||
|
||||
SSLContext ctx = null;
|
||||
try {
|
||||
ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(null, certs, new SecureRandom());
|
||||
} catch (final java.security.GeneralSecurityException ex) {
|
||||
}
|
||||
|
||||
try {
|
||||
final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(final String hostname,
|
||||
final SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
client.setHostnameVerifier(hostnameVerifier);
|
||||
client.setSslSocketFactory(ctx.getSocketFactory());
|
||||
} catch (final Exception e) {
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public static OkHttpClient createClient() {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
return configureClient(client);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Callback createCallback(final String url) {
|
||||
|
||||
return new Callback() {
|
||||
@Override
|
||||
public void success(final Object o, final Response response) {
|
||||
logger.info("URL : " + url + "\tStatus : "
|
||||
+ response.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(final RetrofitError retrofitError) {
|
||||
logger.info(retrofitError.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static WebHookService createWebHookService(final String url) {
|
||||
|
||||
final OkHttpClient client = ProcessorHelper.createClient();
|
||||
|
||||
final RestAdapter restAdapter = new RestAdapter.Builder()
|
||||
.setEndpoint(url).setClient(new OkClient(client)).build();
|
||||
|
||||
return restAdapter.create(WebHookService.class);
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.processor;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.apiKeyName;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookConfiguration;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookConfigurationRepository;
|
||||
import org.mifosplatform.infrastructure.hooks.processor.data.SmsProviderData;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import retrofit.Callback;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
@Service
|
||||
public class TwilioHookProcessor implements HookProcessor {
|
||||
|
||||
private final HookConfigurationRepository hookConfigurationRepository;
|
||||
|
||||
@Autowired
|
||||
public TwilioHookProcessor(
|
||||
final HookConfigurationRepository hookConfigurationRepository) {
|
||||
this.hookConfigurationRepository = hookConfigurationRepository;
|
||||
}
|
||||
|
||||
@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 SmsProviderData smsProviderData = new SmsProviderData(
|
||||
hook.getHookConfig());
|
||||
|
||||
sendRequest(smsProviderData, payload, entityName, actionName,
|
||||
tenantIdentifier, authToken, hook);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void sendRequest(final SmsProviderData smsProviderData,
|
||||
final String payload, final String entityName,
|
||||
final String actionName, final String tenantIdentifier,
|
||||
final String authToken, final Hook hook) {
|
||||
|
||||
final WebHookService service = ProcessorHelper
|
||||
.createWebHookService(smsProviderData.getUrl());
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Callback callback = ProcessorHelper
|
||||
.createCallback(smsProviderData.getUrl());
|
||||
|
||||
String apiKey = this.hookConfigurationRepository
|
||||
.findOneByHookIdAndFieldName(hook.getId(), apiKeyName);
|
||||
if (apiKey == null) {
|
||||
smsProviderData.setUrl(null);
|
||||
smsProviderData.setEndpoint(System.getProperty("baseUrl"));
|
||||
smsProviderData.setTenantId(tenantIdentifier);
|
||||
smsProviderData.setMifosToken(authToken);
|
||||
apiKey = service.sendSmsBridgeConfigRequest(smsProviderData);
|
||||
final HookConfiguration apiKeyEntry = HookConfiguration.createNew(
|
||||
hook, "string", apiKeyName, apiKey);
|
||||
this.hookConfigurationRepository.save(apiKeyEntry);
|
||||
}
|
||||
|
||||
if (apiKey != null && !apiKey.equals("")) {
|
||||
final JsonObject json = new JsonParser().parse(payload)
|
||||
.getAsJsonObject();
|
||||
service.sendSmsBridgeRequest(entityName, actionName,
|
||||
tenantIdentifier, apiKey, json, callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.processor;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.contentTypeName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.payloadURLName;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookConfiguration;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import retrofit.Callback;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
@Service
|
||||
public class WebHookProcessor implements HookProcessor {
|
||||
|
||||
@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,
|
||||
final String authToken) {
|
||||
|
||||
final String mifosEndpointUrl = System.getProperty("baseUrl");
|
||||
final WebHookService service = ProcessorHelper
|
||||
.createWebHookService(url);
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Callback callback = ProcessorHelper.createCallback(url);
|
||||
|
||||
if (contentType.equalsIgnoreCase("json")
|
||||
|| contentType.contains("json")) {
|
||||
final JsonObject json = new JsonParser().parse(payload)
|
||||
.getAsJsonObject();
|
||||
json.addProperty("mifosToken", authToken);
|
||||
service.sendJsonRequest(entityName, actionName, tenantIdentifier,
|
||||
mifosEndpointUrl, json, callback);
|
||||
} else {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map = new Gson().fromJson(payload, map.getClass());
|
||||
map.put("mifosToken", authToken);
|
||||
service.sendFormRequest(entityName, actionName, tenantIdentifier,
|
||||
mifosEndpointUrl, map, callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.processor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.processor.data.SmsProviderData;
|
||||
|
||||
import retrofit.Callback;
|
||||
import retrofit.client.Response;
|
||||
import retrofit.http.Body;
|
||||
import retrofit.http.FieldMap;
|
||||
import retrofit.http.FormUrlEncoded;
|
||||
import retrofit.http.Header;
|
||||
import retrofit.http.POST;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public interface WebHookService {
|
||||
|
||||
final static String ENTITY_HEADER = "X-Mifos-Entity";
|
||||
final static String ACTION_HEADER = "X-Mifos-Action";
|
||||
final static String TENANT_HEADER = "X-Mifos-Platform-TenantId";
|
||||
final static String ENDPOINT_HEADER = "X-Mifos-Endpoint";
|
||||
final static String API_KEY_HEADER = "X-Mifos-API-Key";
|
||||
|
||||
// Template - Web
|
||||
@POST("/")
|
||||
void sendJsonRequest(@Header(ENTITY_HEADER) String entityHeader,
|
||||
@Header(ACTION_HEADER) String actionHeader,
|
||||
@Header(TENANT_HEADER) String tenantHeader,
|
||||
@Header(ENDPOINT_HEADER) String endpointHeader,
|
||||
@Body JsonObject result, Callback<Response> callBack);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/")
|
||||
void sendFormRequest(@Header(ENTITY_HEADER) String entityHeader,
|
||||
@Header(ACTION_HEADER) String actionHeader,
|
||||
@Header(TENANT_HEADER) String tenantHeader,
|
||||
@Header(ENDPOINT_HEADER) String endpointHeader,
|
||||
@FieldMap Map<String, String> params, Callback<Response> callBack);
|
||||
|
||||
// Template - SMS Bridge
|
||||
@POST("/")
|
||||
void sendSmsBridgeRequest(@Header(ENTITY_HEADER) String entityHeader,
|
||||
@Header(ACTION_HEADER) String actionHeader,
|
||||
@Header(TENANT_HEADER) String tenantHeader,
|
||||
@Header(API_KEY_HEADER) String apiKeyHeader,
|
||||
@Body JsonObject result, Callback<Response> callBack);
|
||||
|
||||
@POST("/configuration")
|
||||
String sendSmsBridgeConfigRequest(@Body SmsProviderData config);
|
||||
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.processor.data;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.payloadURLName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.phoneNumberName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.smsProviderAccountIdName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.smsProviderName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.smsProviderTokenIdName;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookConfiguration;
|
||||
|
||||
public class SmsProviderData {
|
||||
|
||||
private String url;
|
||||
private String phoneNo;
|
||||
private String smsProvider;
|
||||
private String smsProviderAccountId;
|
||||
private String smsProviderToken;
|
||||
|
||||
private String tenantId;
|
||||
private String mifosToken;
|
||||
private String endpoint;
|
||||
|
||||
public SmsProviderData(final Set<HookConfiguration> config) {
|
||||
|
||||
for (final HookConfiguration conf : config) {
|
||||
final String fieldName = conf.getFieldName();
|
||||
if (fieldName.equals(payloadURLName)) {
|
||||
this.url = conf.getFieldValue();
|
||||
}
|
||||
if (fieldName.equals(smsProviderName)) {
|
||||
this.smsProvider = conf.getFieldValue();
|
||||
}
|
||||
if (fieldName.equals(smsProviderAccountIdName)) {
|
||||
this.smsProviderAccountId = conf.getFieldValue();
|
||||
}
|
||||
if (fieldName.equals(smsProviderTokenIdName)) {
|
||||
this.smsProviderToken = conf.getFieldValue();
|
||||
}
|
||||
if (fieldName.equals(phoneNumberName)) {
|
||||
this.phoneNo = conf.getFieldValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNo;
|
||||
}
|
||||
|
||||
public String getSmsProvider() {
|
||||
return smsProvider;
|
||||
}
|
||||
|
||||
public String getSmsProviderAccountId() {
|
||||
return smsProviderAccountId;
|
||||
}
|
||||
|
||||
public String getSmsProviderTokenId() {
|
||||
return smsProviderToken;
|
||||
}
|
||||
|
||||
public String getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(String tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public String getMifosToken() {
|
||||
return mifosToken;
|
||||
}
|
||||
|
||||
public void setMifosToken(String mifosToken) {
|
||||
this.mifosToken = mifosToken;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public void setEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.serialization;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@Component
|
||||
public class HookCommandFromApiJsonDeserializer {
|
||||
|
||||
/**
|
||||
* The parameters supported for this command.
|
||||
*/
|
||||
private final Set<String> supportedParameters = new HashSet<>(
|
||||
Arrays.asList("name", "displayName", "isActive", "events", "config"));
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public HookCommandFromApiJsonDeserializer(
|
||||
final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
this.supportedParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
|
||||
dataValidationErrors).resource("hook");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed("name",
|
||||
element);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
this.supportedParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
|
||||
dataValidationErrors).resource("hook");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
if (this.fromApiJsonHelper.parameterExists("name", element)) {
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(
|
||||
"name", element);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(
|
||||
final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(
|
||||
"validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.data.HookData;
|
||||
|
||||
public interface HookReadPlatformService {
|
||||
|
||||
Collection<HookData> retrieveAllHooks();
|
||||
|
||||
HookData retrieveHook(Long hookId);
|
||||
|
||||
HookData retrieveNewHookDetails(String templateName);
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
|
||||
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
|
||||
import org.mifosplatform.infrastructure.hooks.data.Event;
|
||||
import org.mifosplatform.infrastructure.hooks.data.EventResultSetExtractor;
|
||||
import org.mifosplatform.infrastructure.hooks.data.Field;
|
||||
import org.mifosplatform.infrastructure.hooks.data.Grouping;
|
||||
import org.mifosplatform.infrastructure.hooks.data.HookData;
|
||||
import org.mifosplatform.infrastructure.hooks.data.HookTemplateData;
|
||||
import org.mifosplatform.infrastructure.hooks.exception.HookNotFoundException;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class HookReadPlatformServiceImpl implements HookReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@Autowired
|
||||
public HookReadPlatformServiceImpl(final PlatformSecurityContext context,
|
||||
final RoutingDataSource dataSource) {
|
||||
this.context = context;
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<HookData> retrieveAllHooks() {
|
||||
this.context.authenticatedUser();
|
||||
final HookMapper rm = new HookMapper(this.jdbcTemplate);
|
||||
final String sql = "select " + rm.schema() + " order by h.name";
|
||||
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookData retrieveHook(final Long hookId) {
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
final HookMapper rm = new HookMapper(this.jdbcTemplate);
|
||||
final String sql = "select " + rm.schema() + " where h.id = ?";
|
||||
|
||||
return this.jdbcTemplate.queryForObject(sql, rm,
|
||||
new Object[] { hookId });
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new HookNotFoundException(hookId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookData retrieveNewHookDetails(final String templateName) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
final TemplateMapper rm = new TemplateMapper(this.jdbcTemplate);
|
||||
final String sql;
|
||||
List<HookTemplateData> templateData;
|
||||
|
||||
if (templateName == null) {
|
||||
sql = "select " + rm.schema() + " order by s.name";
|
||||
templateData = this.jdbcTemplate.query(sql, rm, new Object[] {});
|
||||
} else {
|
||||
sql = "select " + rm.schema() + " where s.name = ? order by s.name";
|
||||
templateData = this.jdbcTemplate.query(sql, rm,
|
||||
new Object[] { templateName });
|
||||
}
|
||||
|
||||
final List<Grouping> events = getTemplateForEvents();
|
||||
|
||||
return HookData.template(templateData, events);
|
||||
}
|
||||
|
||||
private List<Grouping> getTemplateForEvents() {
|
||||
final String sql = "select p.grouping, p.entity_name, p.action_name from m_permission p "
|
||||
+ " where p.action_name NOT LIKE '%CHECKER%' AND p.action_name NOT LIKE '%READ%' "
|
||||
+ " order by p.grouping, p.entity_name ";
|
||||
final EventResultSetExtractor extractor = new EventResultSetExtractor();
|
||||
return this.jdbcTemplate.query(sql, extractor);
|
||||
}
|
||||
|
||||
private static final class HookMapper implements RowMapper<HookData> {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public HookMapper(final JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return " h.id, s.name as name, h.name as display_name, h.is_active, h.created_date, h.lastmodified_date "
|
||||
+ " from m_hook h inner join m_hook_templates s on h.template_id = s.id ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookData mapRow(final ResultSet rs,
|
||||
@SuppressWarnings("unused") final int rowNum)
|
||||
throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String name = rs.getString("name");
|
||||
final String displayname = rs.getString("display_name");
|
||||
final boolean isActive = rs.getBoolean("is_active");
|
||||
final LocalDate createdAt = JdbcSupport.getLocalDate(rs,
|
||||
"created_date");
|
||||
final LocalDate updatedAt = JdbcSupport.getLocalDate(rs,
|
||||
"lastmodified_date");
|
||||
final List<Event> registeredEvents = retrieveEvents(id);
|
||||
final List<Field> config = retrieveConfig(id);
|
||||
|
||||
return HookData.instance(id, name, displayname, isActive,
|
||||
createdAt, updatedAt, registeredEvents, config);
|
||||
}
|
||||
|
||||
private List<Event> retrieveEvents(final Long hookId) {
|
||||
|
||||
final HookEventMapper rm = new HookEventMapper();
|
||||
final String sql = "select " + rm.schema() + " where h.id= ?";
|
||||
|
||||
return this.jdbcTemplate.query(sql, rm, new Object[] { hookId });
|
||||
}
|
||||
|
||||
private List<Field> retrieveConfig(final Long hookId) {
|
||||
|
||||
final HookConfigMapper rm = new HookConfigMapper();
|
||||
final String sql = "select " + rm.schema()
|
||||
+ " where h.id= ? order by hc.field_name";
|
||||
|
||||
final List<Field> fields = this.jdbcTemplate.query(sql, rm,
|
||||
new Object[] { hookId });
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HookEventMapper implements RowMapper<Event> {
|
||||
|
||||
public String schema() {
|
||||
return " re.action_name, re.entity_name from m_hook h inner join m_hook_registered_events re on h.id = re.hook_id ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event mapRow(final ResultSet rs,
|
||||
@SuppressWarnings("unused") final int rowNum)
|
||||
throws SQLException {
|
||||
final String actionName = rs.getString("action_name");
|
||||
final String entityName = rs.getString("entity_name");
|
||||
return Event.instance(actionName, entityName);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HookConfigMapper implements RowMapper<Field> {
|
||||
|
||||
public String schema() {
|
||||
return " hc.field_name, hc.field_value from m_hook h inner join m_hook_configuration hc on h.id = hc.hook_id ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field mapRow(final ResultSet rs,
|
||||
@SuppressWarnings("unused") final int rowNum)
|
||||
throws SQLException {
|
||||
final String fieldName = rs.getString("field_name");
|
||||
final String fieldValue = rs.getString("field_value");
|
||||
return Field.fromConfig(fieldName, fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TemplateMapper implements
|
||||
RowMapper<HookTemplateData> {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public TemplateMapper(final JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return " s.id, s.name from m_hook_templates s ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookTemplateData mapRow(final ResultSet rs,
|
||||
@SuppressWarnings("unused") final int rowNum)
|
||||
throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String name = rs.getString("name");
|
||||
final List<Field> schema = retrieveSchema(id);
|
||||
|
||||
return HookTemplateData.instance(id, name, schema);
|
||||
}
|
||||
|
||||
private List<Field> retrieveSchema(final Long templateId) {
|
||||
|
||||
final TemplateSchemaMapper rm = new TemplateSchemaMapper();
|
||||
final String sql = "select " + rm.schema()
|
||||
+ " where s.id= ? order by hs.field_name ";
|
||||
|
||||
final List<Field> fields = this.jdbcTemplate.query(sql, rm,
|
||||
new Object[] { templateId });
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TemplateSchemaMapper implements RowMapper<Field> {
|
||||
|
||||
public String schema() {
|
||||
return " hs.field_type, hs.field_name, hs.placeholder, hs.optional from m_hook_templates s "
|
||||
+ " inner join m_hook_schema hs on s.id = hs.hook_template_id ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field mapRow(final ResultSet rs,
|
||||
@SuppressWarnings("unused") final int rowNum)
|
||||
throws SQLException {
|
||||
final String fieldName = rs.getString("field_name");
|
||||
final String fieldType = rs.getString("field_type");
|
||||
final Boolean optional = rs.getBoolean("optional");
|
||||
final String placeholder = rs.getString("placeholder");
|
||||
return Field
|
||||
.fromSchema(fieldType, fieldName, optional, placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.service;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface HookWritePlatformService {
|
||||
|
||||
CommandProcessingResult createHook(JsonCommand command);
|
||||
|
||||
CommandProcessingResult updateHook(Long hookId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult deleteHook(Long hookId);
|
||||
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.infrastructure.hooks.service;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.actionNameParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.configParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.entityNameParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.eventsParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.nameParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.webTemplateName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookConfiguration;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookRepository;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookResource;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookTemplate;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookTemplateRepository;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Schema;
|
||||
import org.mifosplatform.infrastructure.hooks.exception.HookNotFoundException;
|
||||
import org.mifosplatform.infrastructure.hooks.exception.HookTemplateNotFoundException;
|
||||
import org.mifosplatform.infrastructure.hooks.serialization.HookCommandFromApiJsonDeserializer;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@Service
|
||||
public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
HookWritePlatformService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final HookRepository hookRepository;
|
||||
private final HookTemplateRepository hookTemplateRepository;
|
||||
private final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public HookWritePlatformServiceJpaRepositoryImpl(
|
||||
final PlatformSecurityContext context,
|
||||
final HookRepository hookRepository,
|
||||
final HookTemplateRepository hookTemplateRepository,
|
||||
final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
final FromJsonHelper fromApiJsonHelper) {
|
||||
this.context = context;
|
||||
this.hookRepository = hookRepository;
|
||||
this.hookTemplateRepository = hookTemplateRepository;
|
||||
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createHook(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
final HookTemplate template = retrieveHookTemplateBy(command
|
||||
.stringValueOfParameterNamed(nameParamName));
|
||||
final String configJson = command.jsonFragment(configParamName);
|
||||
final Set<HookConfiguration> config = assembleConfig(
|
||||
command.mapValueOfParameterNamed(configJson), template);
|
||||
final JsonArray events = command
|
||||
.arrayOfParameterNamed(eventsParamName);
|
||||
final Set<HookResource> allEvents = assembleSetOfEvents(events);
|
||||
final Hook hook = Hook.fromJson(command, template, config,
|
||||
allEvents);
|
||||
|
||||
validateHookRules(template, config);
|
||||
|
||||
this.hookRepository.save(hook);
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withCommandId(command.commandId())
|
||||
.withEntityId(hook.getId()).build();
|
||||
} catch (final DataIntegrityViolationException dve) {
|
||||
handleHookDataIntegrityIssues(command, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateHook(final Long hookId,
|
||||
final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
||||
|
||||
final Hook hook = retrieveHookBy(hookId);
|
||||
final HookTemplate template = hook.getHookTemplate();
|
||||
final Map<String, Object> changes = hook.update(command);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
|
||||
if (changes.containsKey(eventsParamName)) {
|
||||
final Set<HookResource> events = assembleSetOfEvents(command
|
||||
.arrayOfParameterNamed(eventsParamName));
|
||||
final boolean updated = hook.updateEvents(events);
|
||||
if (!updated) {
|
||||
changes.remove(eventsParamName);
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.containsKey(configParamName)) {
|
||||
final String configJson = command
|
||||
.jsonFragment(configParamName);
|
||||
final Set<HookConfiguration> config = assembleConfig(
|
||||
command.mapValueOfParameterNamed(configJson),
|
||||
template);
|
||||
final boolean updated = hook.updateConfig(config);
|
||||
if (!updated) {
|
||||
changes.remove(configParamName);
|
||||
}
|
||||
}
|
||||
|
||||
this.hookRepository.saveAndFlush(hook);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(hookId) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
} catch (final DataIntegrityViolationException dve) {
|
||||
handleHookDataIntegrityIssues(command, dve);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult deleteHook(final Long hookId) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
|
||||
final Hook hook = retrieveHookBy(hookId);
|
||||
|
||||
try {
|
||||
this.hookRepository.delete(hook);
|
||||
this.hookRepository.flush();
|
||||
} catch (final DataIntegrityViolationException e) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: "
|
||||
+ e.getMostSpecificCause());
|
||||
}
|
||||
return new CommandProcessingResultBuilder().withEntityId(hookId)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Hook retrieveHookBy(final Long hookId) {
|
||||
final Hook hook = this.hookRepository.findOne(hookId);
|
||||
if (hook == null) {
|
||||
throw new HookNotFoundException(hookId.toString());
|
||||
}
|
||||
return hook;
|
||||
}
|
||||
|
||||
private HookTemplate retrieveHookTemplateBy(final String templateName) {
|
||||
final HookTemplate template = this.hookTemplateRepository
|
||||
.findOne(templateName);
|
||||
if (template == null) {
|
||||
throw new HookTemplateNotFoundException(templateName);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
private Set<HookConfiguration> assembleConfig(
|
||||
final Map<String, String> hookConfig, final HookTemplate template) {
|
||||
|
||||
final Set<HookConfiguration> configuration = new HashSet<>();
|
||||
final Set<Schema> fields = template.getSchema();
|
||||
|
||||
for (final Entry<String, String> configEntry : hookConfig.entrySet()) {
|
||||
for (final Schema field : fields) {
|
||||
if (field.getFieldName().equalsIgnoreCase(configEntry.getKey())) {
|
||||
final HookConfiguration config = HookConfiguration
|
||||
.createNewWithoutHook(field.getFieldType(),
|
||||
configEntry.getKey(),
|
||||
configEntry.getValue());
|
||||
configuration.add(config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
private Set<HookResource> assembleSetOfEvents(final JsonArray eventsArray) {
|
||||
|
||||
final Set<HookResource> allEvents = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < eventsArray.size(); i++) {
|
||||
|
||||
final JsonObject eventElement = eventsArray.get(i)
|
||||
.getAsJsonObject();
|
||||
|
||||
final String entityName = this.fromApiJsonHelper
|
||||
.extractStringNamed(entityNameParamName, eventElement);
|
||||
final String actionName = this.fromApiJsonHelper
|
||||
.extractStringNamed(actionNameParamName, eventElement);
|
||||
final HookResource event = HookResource.createNewWithoutHook(
|
||||
entityName, actionName);
|
||||
allEvents.add(event);
|
||||
}
|
||||
|
||||
return allEvents;
|
||||
}
|
||||
|
||||
private void validateHookRules(final HookTemplate template,
|
||||
final Set<HookConfiguration> config) {
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
|
||||
dataValidationErrors).resource("hook");
|
||||
|
||||
if (!template.getName().equalsIgnoreCase(webTemplateName)
|
||||
&& this.hookRepository.findOneByTemplateId(template.getId()) != null) {
|
||||
final String errorMessage = "multiple.non.web.template.hooks.not.supported";
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
final Set<Schema> fields = template.getSchema();
|
||||
for (final Schema field : fields) {
|
||||
if (!field.isOptional()) {
|
||||
boolean found = false;
|
||||
for (final HookConfiguration conf : config) {
|
||||
if (field.getFieldName().equals(conf.getFieldName())) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
final String errorMessage = "required.config.field."
|
||||
+ field.getFieldName() + ".not.provided";
|
||||
baseDataValidator.reset()
|
||||
.failWithCodeNoParameterAddedToErrorCode(
|
||||
errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHookDataIntegrityIssues(final JsonCommand command,
|
||||
final DataIntegrityViolationException dve) {
|
||||
final Throwable realCause = dve.getMostSpecificCause();
|
||||
if (realCause.getMessage().contains("hook_name")) {
|
||||
final String name = command.stringValueOfParameterNamed("name");
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.hook.duplicate.name", "A hook with name '"
|
||||
+ name + "' already exists", "name", name);
|
||||
}
|
||||
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: "
|
||||
+ realCause.getMessage());
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -53,7 +53,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
|
||||
|
||||
private static boolean firstRequestProcessed = false;
|
||||
private final static Logger logger = LoggerFactory.getLogger(TenantAwareBasicAuthenticationFilter.class);
|
||||
|
||||
|
||||
private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
|
||||
private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
@@ -103,8 +103,16 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
|
||||
final MifosPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier);
|
||||
|
||||
ThreadLocalContextUtil.setTenant(tenant);
|
||||
String authToken = request.getHeader("Authorization");
|
||||
|
||||
if (authToken != null && authToken.startsWith("Basic ")) {
|
||||
ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("Basic ", ""));
|
||||
}
|
||||
|
||||
if (!firstRequestProcessed) {
|
||||
final String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/");
|
||||
System.setProperty("baseUrl", baseUrl);
|
||||
|
||||
final boolean ehcacheEnabled = this.configurationDomainService.isEhcacheEnabled();
|
||||
if (ehcacheEnabled) {
|
||||
this.cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE);
|
||||
@@ -114,7 +122,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
|
||||
TenantAwareBasicAuthenticationFilter.firstRequestProcessed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
super.doFilter(req, res, chain);
|
||||
} catch (final InvalidTenantIdentiferException e) {
|
||||
// deal with exception at low level
|
||||
|
||||
@@ -72,4 +72,10 @@
|
||||
|
||||
<import resource="cache.xml" />
|
||||
|
||||
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
|
||||
<property name="taskExecutor">
|
||||
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
||||
Reference in New Issue
Block a user