This commit is contained in:
avikganguly01
2014-09-17 19:46:01 +05:30
parent 7889254e78
commit 68e0b873fe
48 changed files with 3066 additions and 5 deletions
+314
View File
@@ -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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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);
+5
View File
@@ -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");
}
@@ -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";
@@ -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);
}
}
@@ -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;
@@ -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();
}
}
@@ -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));
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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> {
}
@@ -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);
}
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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);
}
@@ -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);
}
}
}
@@ -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);
}
@@ -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());
}
}
@@ -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>