Merge branch 'MIFOSX-1786' of git://github.com/binaryking/mifosx into binaryking-MIFOSX-1786

This commit is contained in:
Vishwas Babu A J
2015-01-13 11:10:16 -08:00
14 changed files with 2069 additions and 1895 deletions
+35 -29
View File
@@ -13936,6 +13936,7 @@ Request Body:
<li> events - array - Determines what events the hook is triggered for. <li> events - array - Determines what events the hook is triggered for.
</li> </li>
<li> config - hash - Required. Key/value pairs to provide settings for this hook. These settings vary between the templates.</li> <li> config - hash - Required. Key/value pairs to provide settings for this hook. These settings vary between the templates.</li>
<li> templateId - Optional. The UGD template ID associated with the same entity (client or loan).</li>
<ul></p> <ul></p>
</div> </div>
<div class="method-example"> <div class="method-example">
@@ -13950,6 +13951,7 @@ Request Body:
"name": "Web", "name": "Web",
"isActive": true, "isActive": true,
"displayName": "Kremlin", "displayName": "Kremlin",
"templateId": 1,
"events": [ "events": [
{ {
"actionName": "DISBURSE", "actionName": "DISBURSE",
@@ -13996,6 +13998,8 @@ GET https://DomainName/api/v1/hooks
"isActive": true, "isActive": true,
"createdAt": [2014, 9, 16], "createdAt": [2014, 9, 16],
"updatedAt": [2014, 9, 16], "updatedAt": [2014, 9, 16],
"templateId": 1,
"templateName": "My UGD",
"events": [ "events": [
{ {
"actionName": "DISBURSE", "actionName": "DISBURSE",
@@ -14043,6 +14047,8 @@ GET https://DomainName/api/v1/hooks/{hookId}
"isActive": true, "isActive": true,
"createdAt": [2014, 9, 16], "createdAt": [2014, 9, 16],
"updatedAt": [2014, 9, 16], "updatedAt": [2014, 9, 16],
"templateId": 1,
"templateName": "My UGD",
"events": [ "events": [
{ {
"actionName": "DISBURSE", "actionName": "DISBURSE",
@@ -33260,7 +33266,7 @@ No Request Body:
<p>User Generated Documents (and other types of templates) can aggregate data from several Mifos X back-end API calls via <b>mappers</b>. Mappers can even access non-Mifos X REST services from other servers. UGDs can render such data in tables, show images, etc. <i>TBD: Please have a look at some of the Example UGDs included in Mifos X (or <a href="https://mifosforge.jira.com/wiki/display/RES/UGD_FinalDoc">the Wiki page</a>, for now.).</i></p> <p>User Generated Documents (and other types of templates) can aggregate data from several Mifos X back-end API calls via <b>mappers</b>. Mappers can even access non-Mifos X REST services from other servers. UGDs can render such data in tables, show images, etc. <i>TBD: Please have a look at some of the Example UGDs included in Mifos X (or <a href="https://mifosforge.jira.com/wiki/display/RES/UGD_FinalDoc">the Wiki page</a>, for now.).</i></p>
<p>UGDs can be assigned to an entity like <b>client</b> or <b>loan</b> and be of a typ like <b>document</b>. The entity and type of a UGD is only there for the convenience of user agents (UIs), in order to know where to show UGDs for the user (i.e. which tab). The Template Engine back-end runner does not actually need this metadata.</p> <p>UGDs can be assigned to an entity like <b>client</b> or <b>loan</b> and be of a type like <b>Document</b> or <b>SMS</b>. The entity and type of a UGD is only there for the convenience of user agents (UIs), in order to know where to show UGDs for the user (i.e. which tab). The Template Engine back-end runner does not actually need this metadata.</p>
<table class=matrixHeading> <table class=matrixHeading>
<tr class="matrixHeadingBG"> <tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td> <td><div class="mifosXHeading2">Field Descriptions</div></td>
@@ -33277,8 +33283,8 @@ No Request Body:
</tr> </tr>
<tr> <tr>
<td class=fielddesc> <td class=fielddesc>
For now only the type document is supported. For now only the types Document and SMS are supported.
In a next version UGDs may be created for e-mails oder SMS. In a next version, UGDs may be created for e-mails as well.
</td> </td>
</tr> </tr>
<tr class=alt> <tr class=alt>
@@ -33357,14 +33363,14 @@ GET https://DomainName/api/v1/templates/template
{ {
"id": 1, "id": 1,
"name": "Test", "name": "Test",
"entity": "loan", "entity": 1,
"type": "Document", "type": 0,
"text": "This is a loan for {{loan.clientName}}", "text": "This is a loan for {{loan.clientName}}",
"mappers": [ "mappers": [
{ {
"mapperorder": 0, "mappersorder": 0,
"mapperkey": "loan", "mapperskey": "loan",
"mappervalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}", "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
"id": 1 "id": 1
} }
] ]
@@ -33401,14 +33407,14 @@ Request Body:
{ {
"id": 1, "id": 1,
"name": "Test", "name": "Test",
"entity": "loan", "entity": 1,
"type": "Document", "type": 0,
"text": "This is a loan for {{loan.clientName}}", "text": "This is a loan for {{loan.clientName}}",
"mappers": [ "mappers": [
{ {
"mapperorder": 0, "mappersorder": 0,
"mapperkey": "loan", "mapperskey": "loan",
"mappervalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}", "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
"id": 1 "id": 1
} }
] ]
@@ -33458,7 +33464,7 @@ Request Body:
<tr> <tr>
<td></td> <td></td>
<td></td> <td></td>
<td>SMS (not yet)</td> <td>SMS</td>
<td>2</td> <td>2</td>
</tr> </tr>
</table> </table>
@@ -33472,14 +33478,14 @@ GET https://DomainName/api/v1/templates
{ {
"id": 1, "id": 1,
"name": "Test", "name": "Test",
"entity": "loan", "entity": 1,
"type": "Document", "type": 0,
"text": "This is a loan for {{loan.clientName}}", "text": "This is a loan for {{loan.clientName}}",
"mappers": [ "mappers": [
{ {
"mapperorder": 0, "mappersorder": 0,
"mapperkey": "loan", "mapperskey": "loan",
"mappervalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}", "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
"id": 1 "id": 1
} }
] ]
@@ -33505,14 +33511,14 @@ GET https://DomainName/api/v1/templates/{Id}
{ {
"id": 1, "id": 1,
"name": "Test", "name": "Test",
"entity": "loan", "entity": 1,
"type": "Document", "type": 0,
"text": "This is a loan for {{loan.clientName}}", "text": "This is a loan for {{loan.clientName}}",
"mappers": [ "mappers": [
{ {
"mapperorder": 0, "mappersorder": 0,
"mapperkey": "loan", "mapperskey": "loan",
"mappervalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}", "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
"id": 1 "id": 1
} }
] ]
@@ -33538,14 +33544,14 @@ Request Body:
{ {
"id": 1, "id": 1,
"name": "Test", "name": "Test",
"entity": "loan", "entity": 1,
"type": "Document", "type": 0,
"text": "This is a loan for {{loan.clientName}}", "text": "This is a loan for {{loan.clientName}}",
"mappers": [ "mappers": [
{ {
"mapperorder": 0, "mappersorder": 0,
"mapperkey": "loan", "mapperskey": "loan",
"mappervalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}", "mappersvalue": "loans/{{loanId}}?associations=all&tenantIdentifier={{tenantIdentifier}}",
"id": 1 "id": 1
} }
] ]
@@ -11,46 +11,52 @@ import java.util.Set;
public class HookApiConstants { public class HookApiConstants {
public static final String HOOK_RESOURCE_NAME = "HOOK"; public static final String HOOK_RESOURCE_NAME = "HOOK";
public static final String nameParamName = "name"; public static final String nameParamName = "name";
public static final String displayNameParamName = "displayName"; public static final String displayNameParamName = "displayName";
public static final String isActiveParamName = "isActive"; public static final String isActiveParamName = "isActive";
public static final String webTemplateName = "Web"; public static final String webTemplateName = "Web";
public static final String smsTemplateName = "SMS Bridge"; public static final String smsTemplateName = "SMS Bridge";
public static final String payloadURLName = "Payload URL"; public static final String payloadURLName = "Payload URL";
public static final String contentTypeName = "Content Type"; public static final String contentTypeName = "Content Type";
public static final String smsProviderName = "SMS Provider"; public static final String smsProviderName = "SMS Provider";
public static final String smsProviderAccountIdName = "SMS Provider Account Id"; public static final String smsProviderAccountIdName = "SMS Provider Account Id";
public static final String smsProviderTokenIdName = "SMS Provider Token"; public static final String smsProviderTokenIdName = "SMS Provider Token";
public static final String phoneNumberName = "Phone Number"; public static final String phoneNumberName = "Phone Number";
public static final String apiKeyName = "Api Key"; public static final String apiKeyName = "Api Key";
public static final String configParamName = "config"; public static final String configParamName = "config";
public static final String eventsParamName = "events"; public static final String eventsParamName = "events";
public static final String entityNameParamName = "entityName"; public static final String entityNameParamName = "entityName";
public static final String actionNameParamName = "actionName"; public static final String actionNameParamName = "actionName";
public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>( public static final String templateIdParamName = "templateId";
Arrays.asList(nameParamName, displayNameParamName,
isActiveParamName, configParamName, eventsParamName));
public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>( public static final String templateNameParamName = "templateName";
Arrays.asList(nameParamName, displayNameParamName,
isActiveParamName, configParamName, eventsParamName)); public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(nameParamName, displayNameParamName,
templateIdParamName, isActiveParamName, configParamName,
eventsParamName, templateNameParamName));
public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(nameParamName, displayNameParamName,
templateIdParamName, isActiveParamName, configParamName,
eventsParamName, templateNameParamName));
} }
@@ -5,87 +5,95 @@
*/ */
package org.mifosplatform.infrastructure.hooks.data; package org.mifosplatform.infrastructure.hooks.data;
import org.joda.time.LocalDate;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import org.joda.time.LocalDate;
public class HookData implements Serializable { public class HookData implements Serializable {
private final Long id; private final Long id;
private final String name; private final String name;
private final String displayName; private final String displayName;
private final Boolean isActive; private final Boolean isActive;
private final LocalDate createdAt; private final LocalDate createdAt;
private final LocalDate updatedAt; private final LocalDate updatedAt;
private final Long templateId;
private final String templateName;
// associations // associations
private final List<Event> events; private final List<Event> events;
private final List<Field> config; private final List<Field> config;
// template data // template data
private final List<HookTemplateData> templates; private final List<HookTemplateData> templates;
private final List<Grouping> groupings; private final List<Grouping> groupings;
public static HookData instance(final Long id, final String name, public static HookData instance(final Long id, final String name,
final String displayName, final boolean isActive, final String displayName, final boolean isActive,
final LocalDate createdAt, final LocalDate updatedAt, final LocalDate createdAt, final LocalDate updatedAt,
final List<Event> registeredEvents, final List<Field> config) { final Long templateId, final List<Event> registeredEvents,
return new HookData(id, name, displayName, isActive, createdAt, final List<Field> config, final String templateName) {
updatedAt, registeredEvents, config, null, null); return new HookData(id, name, displayName, isActive, createdAt,
} updatedAt, templateId, registeredEvents, config, templateName,
null, null);
}
public static HookData template(final List<HookTemplateData> templates, public static HookData template(final List<HookTemplateData> templates,
final List<Grouping> groupings) { final List<Grouping> groupings) {
return new HookData(null, null, null, null, null, null, null, null, return new HookData(null, null, null, null, null, null, null, null,
templates, groupings); null, null, templates, groupings);
} }
public static HookData templateExisting(final HookData hookData, public static HookData templateExisting(final HookData hookData,
final List<HookTemplateData> templates, final List<HookTemplateData> templates,
final List<Grouping> groupings) { final List<Grouping> groupings) {
return new HookData(hookData.id, hookData.name, hookData.displayName, return new HookData(hookData.id, hookData.name, hookData.displayName,
hookData.isActive, hookData.createdAt, hookData.updatedAt, hookData.isActive, hookData.createdAt, hookData.updatedAt,
hookData.events, hookData.config, templates, groupings); hookData.templateId, hookData.events, hookData.config,
} hookData.templateName, templates, groupings);
}
private HookData(final Long id, final String name, private HookData(final Long id, final String name,
final String displayName, final Boolean isActive, final String displayName, final Boolean isActive,
final LocalDate createdAt, final LocalDate updatedAt, final LocalDate createdAt, final LocalDate updatedAt,
final List<Event> events, final List<Field> config, final Long templateId, final List<Event> events,
final List<HookTemplateData> templates, final List<Field> config, final String templateName,
final List<Grouping> groupings) { final List<HookTemplateData> templates,
this.id = id; final List<Grouping> groupings) {
this.name = name; this.id = id;
this.displayName = displayName; this.name = name;
this.isActive = isActive; this.displayName = displayName;
this.createdAt = createdAt; this.isActive = isActive;
this.updatedAt = updatedAt; this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.templateId = templateId;
this.templateName = templateName;
// associations // associations
this.events = events; this.events = events;
this.config = config; this.config = config;
// template // template
this.templates = templates; this.templates = templates;
this.groupings = groupings; this.groupings = groupings;
} }
public Long getHookId() { public Long getHookId() {
return this.id; return this.id;
} }
public List<HookTemplateData> getTemplates() { public List<HookTemplateData> getTemplates() {
return this.templates; return this.templates;
} }
public List<Grouping> getGroupings() { public List<Grouping> getGroupings() {
return this.groupings; return this.groupings;
} }
public String getTemplateName() { public String getTemplateName() {
return this.name; return this.name;
} }
} }
@@ -5,183 +5,198 @@
*/ */
package org.mifosplatform.infrastructure.hooks.domain; package org.mifosplatform.infrastructure.hooks.domain;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.configParamName; import com.google.gson.JsonArray;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.displayNameParamName; import com.google.gson.JsonElement;
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.apache.commons.lang.StringUtils;
import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.LazyCollectionOption;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom; import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.template.domain.Template;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.google.gson.JsonArray; import javax.persistence.*;
import com.google.gson.JsonElement; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.*;
@Entity @Entity
@Table(name = "m_hook") @Table(name = "m_hook")
public class Hook extends AbstractAuditableCustom<AppUser, Long> { public class Hook extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "name", nullable = false, length = 100) @Column(name = "name", nullable = false, length = 100)
private String name; private String name;
@Column(name = "is_active", nullable = false) @Column(name = "is_active", nullable = false)
private Boolean isActive; private Boolean isActive;
@LazyCollection(LazyCollectionOption.FALSE) @LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true) @OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true)
private Set<HookResource> events = new HashSet<>(); private Set<HookResource> events = new HashSet<>();
@LazyCollection(LazyCollectionOption.FALSE) @LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true) @OneToMany(cascade = CascadeType.ALL, mappedBy = "hook", orphanRemoval = true)
private Set<HookConfiguration> config = new HashSet<>(); private Set<HookConfiguration> config = new HashSet<>();
@ManyToOne(optional = true) @ManyToOne(optional = true)
@JoinColumn(name = "template_id", referencedColumnName = "id", nullable = false) @JoinColumn(name = "template_id", referencedColumnName = "id", nullable = false)
private HookTemplate template; private HookTemplate template;
protected Hook() { @ManyToOne(optional = true)
// @JoinColumn(name = "ugd_template_id", referencedColumnName = "id", nullable = false)
} private Template ugdTemplate;
public static Hook fromJson(final JsonCommand command, protected Hook() {
final HookTemplate template, final Set<HookConfiguration> config, //
final Set<HookResource> events) { }
final String displayName = command
.stringValueOfParameterNamed(displayNameParamName); public static Hook fromJson(final JsonCommand command,
Boolean isActive = command final HookTemplate template, final Set<HookConfiguration> config,
.booleanObjectValueOfParameterNamed(isActiveParamName); final Set<HookResource> events, final Template ugdTemplate) {
if(isActive == null) final String displayName = command
.stringValueOfParameterNamed(displayNameParamName);
Boolean isActive = command
.booleanObjectValueOfParameterNamed(isActiveParamName);
if (isActive == null)
isActive = false; isActive = false;
return new Hook(template, displayName, isActive, config, events); return new Hook(template, displayName, isActive, config, events,
} ugdTemplate);
}
private Hook(final HookTemplate template, final String displayName, private Hook(final HookTemplate template, final String displayName,
final Boolean isActive, final Set<HookConfiguration> config, final Boolean isActive, final Set<HookConfiguration> config,
final Set<HookResource> events) { final Set<HookResource> events, final Template ugdTemplate) {
this.template = template; this.template = template;
if (StringUtils.isNotBlank(displayName)) { if (StringUtils.isNotBlank(displayName)) {
this.name = displayName.trim(); this.name = displayName.trim();
} else { } else {
this.name = template.getName(); this.name = template.getName();
} }
this.isActive = isActive; this.isActive = isActive;
if (!CollectionUtils.isEmpty(config)) { if (!CollectionUtils.isEmpty(config)) {
this.config = associateConfigWithThisHook(config); this.config = associateConfigWithThisHook(config);
} }
if (!CollectionUtils.isEmpty(events)) { if (!CollectionUtils.isEmpty(events)) {
this.events = associateEventsWithThisHook(events); this.events = associateEventsWithThisHook(events);
} }
} this.ugdTemplate = ugdTemplate;
}
private Set<HookConfiguration> associateConfigWithThisHook( private Set<HookConfiguration> associateConfigWithThisHook(
final Set<HookConfiguration> config) { final Set<HookConfiguration> config) {
for (final HookConfiguration hookConfiguration : config) { for (final HookConfiguration hookConfiguration : config) {
hookConfiguration.update(this); hookConfiguration.update(this);
} }
return config; return config;
} }
private Set<HookResource> associateEventsWithThisHook( private Set<HookResource> associateEventsWithThisHook(
final Set<HookResource> events) { final Set<HookResource> events) {
for (final HookResource hookResource : events) { for (final HookResource hookResource : events) {
hookResource.update(this); hookResource.update(this);
} }
return events; return events;
} }
public HookTemplate getHookTemplate() { public HookTemplate getHookTemplate() {
return this.template; return this.template;
} }
public Set<HookConfiguration> getHookConfig() { public Template getUgdTemplate() {
return this.config; return this.ugdTemplate;
} }
public Map<String, Object> update(final JsonCommand command) { public Long getUgdTemplateId() {
return this.ugdTemplate.getId();
}
final Map<String, Object> actualChanges = new LinkedHashMap<>(4); public Set<HookConfiguration> getHookConfig() {
return this.config;
}
if (command.isChangeInStringParameterNamed(displayNameParamName, public Map<String, Object> update(final JsonCommand command) {
this.name)) {
final String newValue = command
.stringValueOfParameterNamed(displayNameParamName);
actualChanges.put(displayNameParamName, newValue);
this.name = newValue;
}
if (command.isChangeInBooleanParameterNamed(isActiveParamName, final Map<String, Object> actualChanges = new LinkedHashMap<>(5);
this.isActive)) {
final Boolean newValue = command
.booleanObjectValueOfParameterNamed(isActiveParamName);
actualChanges.put(isActiveParamName, newValue);
this.isActive = newValue;
}
// events if (command.isChangeInStringParameterNamed(displayNameParamName,
if (command.hasParameter(eventsParamName)) { this.name)) {
final JsonArray jsonArray = command final String newValue = command
.arrayOfParameterNamed(eventsParamName); .stringValueOfParameterNamed(displayNameParamName);
if (jsonArray != null) { actualChanges.put(displayNameParamName, newValue);
actualChanges.put(eventsParamName, jsonArray); this.name = newValue;
} }
}
// config if (command.isChangeInBooleanParameterNamed(isActiveParamName,
if (command.hasParameter(configParamName)) { this.isActive)) {
final JsonElement element = command.parsedJson().getAsJsonObject() final Boolean newValue = command
.get(configParamName); .booleanObjectValueOfParameterNamed(isActiveParamName);
if (element != null) { actualChanges.put(isActiveParamName, newValue);
actualChanges.put(configParamName, element); this.isActive = newValue;
} }
}
return actualChanges; if (command.isChangeInLongParameterNamed(templateIdParamName,
} getUgdTemplateId())) {
final Long newValue = command
.longValueOfParameterNamed(templateIdParamName);
actualChanges.put(templateIdParamName, newValue);
}
public boolean updateEvents(final Set<HookResource> newHookEvents) { // events
if (newHookEvents == null) { if (command.hasParameter(eventsParamName)) {
return false; final JsonArray jsonArray = command
} .arrayOfParameterNamed(eventsParamName);
if (jsonArray != null) {
actualChanges.put(eventsParamName, jsonArray);
}
}
if (this.events == null) { // config
this.events = new HashSet<>(); if (command.hasParameter(configParamName)) {
} final JsonElement element = command.parsedJson().getAsJsonObject()
this.events.clear(); .get(configParamName);
this.events.addAll(associateEventsWithThisHook(newHookEvents)); if (element != null) {
return true; actualChanges.put(configParamName, element);
} }
}
public boolean updateConfig(final Set<HookConfiguration> newHookConfig) { return actualChanges;
if (newHookConfig == null) { }
return false;
}
if (this.config == null) { public boolean updateEvents(final Set<HookResource> newHookEvents) {
this.config = new HashSet<>(); if (newHookEvents == null) {
} return false;
this.config.clear(); }
this.config.addAll(associateConfigWithThisHook(newHookConfig));
return true; 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;
}
public void updateUgdTemplate(final Template ugdTemplate) {
this.ugdTemplate = ugdTemplate;
}
} }
@@ -5,8 +5,6 @@
*/ */
package org.mifosplatform.infrastructure.hooks.listener; package org.mifosplatform.infrastructure.hooks.listener;
import java.util.List;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant; import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil; import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.hooks.domain.Hook; import org.mifosplatform.infrastructure.hooks.domain.Hook;
@@ -20,48 +18,50 @@ import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
@Service @Service
public class MifosHookListener implements HookListener { public class MifosHookListener implements HookListener {
private final HookProcessorProvider hookProcessorProvider; private final HookProcessorProvider hookProcessorProvider;
private final HookReadPlatformService hookReadPlatformService; private final HookReadPlatformService hookReadPlatformService;
private final TenantDetailsService tenantDetailsService; private final TenantDetailsService tenantDetailsService;
@Autowired @Autowired
public MifosHookListener(final HookProcessorProvider hookProcessorProvider, public MifosHookListener(final HookProcessorProvider hookProcessorProvider,
final HookReadPlatformService hookReadPlatformService, final HookReadPlatformService hookReadPlatformService,
final TenantDetailsService tenantDetailsService) { final TenantDetailsService tenantDetailsService) {
this.hookReadPlatformService = hookReadPlatformService; this.hookReadPlatformService = hookReadPlatformService;
this.hookProcessorProvider = hookProcessorProvider; this.hookProcessorProvider = hookProcessorProvider;
this.tenantDetailsService = tenantDetailsService; this.tenantDetailsService = tenantDetailsService;
} }
@Override @Override
public void onApplicationEvent(final HookEvent event) { public void onApplicationEvent(final HookEvent event) {
final String tenantIdentifier = event.getTenantIdentifier(); final String tenantIdentifier = event.getTenantIdentifier();
final MifosPlatformTenant tenant = this.tenantDetailsService final MifosPlatformTenant tenant = this.tenantDetailsService
.loadTenantById(tenantIdentifier); .loadTenantById(tenantIdentifier);
ThreadLocalContextUtil.setTenant(tenant); ThreadLocalContextUtil.setTenant(tenant);
final AppUser appUser = event.getAppUser(); final AppUser appUser = event.getAppUser();
final String authToken = event.getAuthToken(); final String authToken = event.getAuthToken();
final HookEventSource hookEventSource = event.getSource(); final HookEventSource hookEventSource = event.getSource();
final String entityName = hookEventSource.getEntityName(); final String entityName = hookEventSource.getEntityName();
final String actionName = hookEventSource.getActionName(); final String actionName = hookEventSource.getActionName();
final String payload = event.getPayload(); final String payload = event.getPayload();
final List<Hook> hooks = this.hookReadPlatformService final List<Hook> hooks = this.hookReadPlatformService
.retrieveHooksByEvent(hookEventSource.getEntityName(), .retrieveHooksByEvent(hookEventSource.getEntityName(),
hookEventSource.getActionName()); hookEventSource.getActionName());
for (final Hook hook : hooks) { for (final Hook hook : hooks) {
final HookProcessor processor = this.hookProcessorProvider final HookProcessor processor = this.hookProcessorProvider
.getProcessor(hook); .getProcessor(hook);
processor.process(hook, appUser, payload, entityName, actionName, processor.process(hook, appUser, payload, entityName, actionName,
tenantIdentifier, authToken); tenantIdentifier, authToken);
} }
} }
} }
@@ -5,79 +5,131 @@
*/ */
package org.mifosplatform.infrastructure.hooks.processor; package org.mifosplatform.infrastructure.hooks.processor;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.apiKeyName; import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.mifosplatform.infrastructure.hooks.domain.Hook; import org.mifosplatform.infrastructure.hooks.domain.Hook;
import org.mifosplatform.infrastructure.hooks.domain.HookConfiguration; import org.mifosplatform.infrastructure.hooks.domain.HookConfiguration;
import org.mifosplatform.infrastructure.hooks.domain.HookConfigurationRepository; import org.mifosplatform.infrastructure.hooks.domain.HookConfigurationRepository;
import org.mifosplatform.infrastructure.hooks.processor.data.SmsProviderData; import org.mifosplatform.infrastructure.hooks.processor.data.SmsProviderData;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepository;
import org.mifosplatform.template.service.TemplateMergeService;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import retrofit.Callback; import retrofit.Callback;
import com.google.gson.JsonObject; import java.io.IOException;
import com.google.gson.JsonParser; import java.util.HashMap;
import java.util.Map;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.apiKeyName;
@Service @Service
public class TwilioHookProcessor implements HookProcessor { public class TwilioHookProcessor implements HookProcessor {
private final HookConfigurationRepository hookConfigurationRepository; private final HookConfigurationRepository hookConfigurationRepository;
private final TemplateMergeService templateMergeService;
private final ClientRepository clientRepository;
@Autowired @Autowired
public TwilioHookProcessor( public TwilioHookProcessor(
final HookConfigurationRepository hookConfigurationRepository) { final HookConfigurationRepository hookConfigurationRepository,
this.hookConfigurationRepository = hookConfigurationRepository; final TemplateMergeService templateMergeService,
} final ClientRepository clientRepository) {
this.hookConfigurationRepository = hookConfigurationRepository;
this.templateMergeService = templateMergeService;
this.clientRepository = clientRepository;
}
@Override @Override
public void process(final Hook hook, public void process(final Hook hook,
@SuppressWarnings("unused") final AppUser appUser, @SuppressWarnings("unused") final AppUser appUser,
final String payload, final String entityName, final String payload, final String entityName,
final String actionName, final String tenantIdentifier, final String actionName, final String tenantIdentifier,
final String authToken) { final String authToken) {
final SmsProviderData smsProviderData = new SmsProviderData( final SmsProviderData smsProviderData = new SmsProviderData(
hook.getHookConfig()); hook.getHookConfig());
sendRequest(smsProviderData, payload, entityName, actionName, sendRequest(smsProviderData, payload, entityName, actionName,
tenantIdentifier, authToken, hook); tenantIdentifier, authToken, hook);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void sendRequest(final SmsProviderData smsProviderData, private void sendRequest(final SmsProviderData smsProviderData,
final String payload, final String entityName, final String payload, String entityName, String actionName,
final String actionName, final String tenantIdentifier, final String tenantIdentifier, final String authToken,
final String authToken, final Hook hook) { final Hook hook) {
final WebHookService service = ProcessorHelper final WebHookService service = ProcessorHelper
.createWebHookService(smsProviderData.getUrl()); .createWebHookService(smsProviderData.getUrl());
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
final Callback callback = ProcessorHelper final Callback callback = ProcessorHelper
.createCallback(smsProviderData.getUrl()); .createCallback(smsProviderData.getUrl());
String apiKey = this.hookConfigurationRepository String apiKey = this.hookConfigurationRepository
.findOneByHookIdAndFieldName(hook.getId(), apiKeyName); .findOneByHookIdAndFieldName(hook.getId(), apiKeyName);
if (apiKey == null) { if (apiKey == null) {
smsProviderData.setUrl(null); smsProviderData.setUrl(null);
smsProviderData.setEndpoint(System.getProperty("baseUrl")); smsProviderData.setEndpoint(System.getProperty("baseUrl"));
smsProviderData.setTenantId(tenantIdentifier); smsProviderData.setTenantId(tenantIdentifier);
smsProviderData.setMifosToken(authToken); smsProviderData.setMifosToken(authToken);
apiKey = service.sendSmsBridgeConfigRequest(smsProviderData); apiKey = service.sendSmsBridgeConfigRequest(smsProviderData);
final HookConfiguration apiKeyEntry = HookConfiguration.createNew( final HookConfiguration apiKeyEntry = HookConfiguration.createNew(
hook, "string", apiKeyName, apiKey); hook, "string", apiKeyName, apiKey);
this.hookConfigurationRepository.save(apiKeyEntry); this.hookConfigurationRepository.save(apiKeyEntry);
} }
if (apiKey != null && !apiKey.equals("")) { if (apiKey != null && !apiKey.equals("")) {
final JsonObject json = new JsonParser().parse(payload) JsonObject json = null;
.getAsJsonObject(); if (hook.getUgdTemplate() != null) {
service.sendSmsBridgeRequest(entityName, actionName, entityName = "sms";
tenantIdentifier, apiKey, json, callback); actionName = "send";
} json = processUgdTemplate(payload, hook, authToken);
if (json == null) {
return;
}
} else {
json = new JsonParser().parse(payload).getAsJsonObject();
}
service.sendSmsBridgeRequest(entityName, actionName,
tenantIdentifier, apiKey, json, callback);
}
} }
private JsonObject processUgdTemplate(final String payload,
final Hook hook, final String authToken) {
JsonObject json = null;
try {
@SuppressWarnings("unchecked")
final HashMap<String, Object> map = new ObjectMapper().readValue(
payload, HashMap.class);
map.put("BASE_URI", System.getProperty("baseUrl"));
if (map.containsKey("clientId")) {
final Long clientId = new Long(Integer.toString((int) map
.get("clientId")));
final Client client = this.clientRepository.findOne(clientId);
final String mobileNo = client.mobileNo();
if (mobileNo != null && !mobileNo.isEmpty()) {
this.templateMergeService.setAuthToken(authToken);
final String compiledMessage = this.templateMergeService
.compile(hook.getUgdTemplate(), map)
.replace("<p>", "").replace("</p>", "");
final Map<String, String> jsonMap = new HashMap<>();
jsonMap.put("mobileNo", mobileNo);
jsonMap.put("message", compiledMessage);
final String jsonString = new Gson().toJson(jsonMap);
json = new JsonParser().parse(jsonString).getAsJsonObject();
}
}
} catch (IOException e) {
}
return json;
}
} }
@@ -5,98 +5,112 @@
*/ */
package org.mifosplatform.infrastructure.hooks.serialization; package org.mifosplatform.infrastructure.hooks.serialization;
import java.lang.reflect.Type; import com.google.gson.JsonElement;
import java.util.ArrayList; import com.google.gson.reflect.TypeToken;
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.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.hooks.api.HookApiConstants;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.google.gson.JsonElement; import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken; import java.util.*;
@Component @Component
public class HookCommandFromApiJsonDeserializer { public class HookCommandFromApiJsonDeserializer {
/** /**
* The parameters supported for this command. * The parameters supported for this command.
*/ */
private final Set<String> supportedParameters = new HashSet<>( private final Set<String> supportedParameters = new HashSet<>(
Arrays.asList("name", "displayName", "isActive", "events", "config")); Arrays.asList("name", "displayName", "isActive", "events",
private final FromJsonHelper fromApiJsonHelper; "config", "templateId"));
private final FromJsonHelper fromApiJsonHelper;
@Autowired @Autowired
public HookCommandFromApiJsonDeserializer( public HookCommandFromApiJsonDeserializer(
final FromJsonHelper fromApiJsonHelper) { final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
} }
public void validateForCreate(final String json) { public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
throw new InvalidJsonException(); throw new InvalidJsonException();
} }
final Type typeOfMap = new TypeToken<Map<String, Object>>() { final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType(); }.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
this.supportedParameters); this.supportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
dataValidationErrors).resource("hook"); dataValidationErrors).resource("hook");
final JsonElement element = this.fromApiJsonHelper.parse(json); final JsonElement element = this.fromApiJsonHelper.parse(json);
final String name = this.fromApiJsonHelper.extractStringNamed("name", final String name = this.fromApiJsonHelper.extractStringNamed("name",
element); element);
baseDataValidator.reset().parameter("name").value(name).notBlank() baseDataValidator.reset().parameter("name").value(name).notBlank()
.notExceedingLengthOf(100); .notExceedingLengthOf(100);
throwExceptionIfValidationWarningsExist(dataValidationErrors); if (this.fromApiJsonHelper.parameterExists(
} HookApiConstants.templateIdParamName, element)) {
final Long templateId = this.fromApiJsonHelper.extractLongNamed(
HookApiConstants.templateIdParamName, element);
baseDataValidator.reset()
.parameter(HookApiConstants.templateIdParamName)
.value(templateId).notNull().integerGreaterThanZero();
}
public void validateForUpdate(final String json) { throwExceptionIfValidationWarningsExist(dataValidationErrors);
if (StringUtils.isBlank(json)) { }
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() { public void validateForUpdate(final String json) {
}.getType(); if (StringUtils.isBlank(json)) {
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, throw new InvalidJsonException();
this.supportedParameters); }
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final Type typeOfMap = new TypeToken<Map<String, Object>>() {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( }.getType();
dataValidationErrors).resource("hook"); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
this.supportedParameters);
final JsonElement element = this.fromApiJsonHelper.parse(json); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
if (this.fromApiJsonHelper.parameterExists("name", element)) { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
final String name = this.fromApiJsonHelper.extractStringNamed( dataValidationErrors).resource("hook");
"name", element);
baseDataValidator.reset().parameter("name").value(name).notBlank()
.notExceedingLengthOf(100);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors); 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);
}
private void throwExceptionIfValidationWarningsExist( if (this.fromApiJsonHelper.parameterExists(
final List<ApiParameterError> dataValidationErrors) { HookApiConstants.templateIdParamName, element)) {
if (!dataValidationErrors.isEmpty()) { final Long templateId = this.fromApiJsonHelper.extractLongNamed(
throw new PlatformApiDataValidationException( HookApiConstants.templateIdParamName, element);
"validation.msg.validation.errors.exist", baseDataValidator.reset()
"Validation errors exist.", dataValidationErrors); .parameter(HookApiConstants.templateIdParamName)
} .value(templateId).notNull().integerGreaterThanZero();
} }
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(
final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
}
} }
@@ -5,20 +5,10 @@
*/ */
package org.mifosplatform.infrastructure.hooks.service; 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.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.hooks.data.Event; import org.mifosplatform.infrastructure.hooks.data.*;
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.domain.Hook; import org.mifosplatform.infrastructure.hooks.domain.Hook;
import org.mifosplatform.infrastructure.hooks.domain.HookRepository; import org.mifosplatform.infrastructure.hooks.domain.HookRepository;
import org.mifosplatform.infrastructure.hooks.exception.HookNotFoundException; import org.mifosplatform.infrastructure.hooks.exception.HookNotFoundException;
@@ -30,227 +20,238 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
@Service @Service
public class HookReadPlatformServiceImpl implements HookReadPlatformService { public class HookReadPlatformServiceImpl implements HookReadPlatformService {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final HookRepository hookRepository; private final HookRepository hookRepository;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
@Autowired @Autowired
public HookReadPlatformServiceImpl(final PlatformSecurityContext context, public HookReadPlatformServiceImpl(final PlatformSecurityContext context,
final HookRepository hookRepository, final HookRepository hookRepository,
final RoutingDataSource dataSource) { final RoutingDataSource dataSource) {
this.context = context; this.context = context;
this.hookRepository = hookRepository; this.hookRepository = hookRepository;
this.jdbcTemplate = new JdbcTemplate(dataSource); this.jdbcTemplate = new JdbcTemplate(dataSource);
} }
@Override @Override
public Collection<HookData> retrieveAllHooks() { public Collection<HookData> retrieveAllHooks() {
this.context.authenticatedUser(); this.context.authenticatedUser();
final HookMapper rm = new HookMapper(this.jdbcTemplate); final HookMapper rm = new HookMapper(this.jdbcTemplate);
final String sql = "select " + rm.schema() + " order by h.name"; final String sql = "select " + rm.schema() + " order by h.name";
return this.jdbcTemplate.query(sql, rm, new Object[] {}); return this.jdbcTemplate.query(sql, rm, new Object[]{});
} }
@Override @Override
public HookData retrieveHook(final Long hookId) { public HookData retrieveHook(final Long hookId) {
try { try {
this.context.authenticatedUser(); this.context.authenticatedUser();
final HookMapper rm = new HookMapper(this.jdbcTemplate); final HookMapper rm = new HookMapper(this.jdbcTemplate);
final String sql = "select " + rm.schema() + " where h.id = ?"; final String sql = "select " + rm.schema() + " where h.id = ?";
return this.jdbcTemplate.queryForObject(sql, rm, return this.jdbcTemplate.queryForObject(sql, rm,
new Object[] { hookId }); new Object[]{hookId});
} catch (final EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
throw new HookNotFoundException(hookId); throw new HookNotFoundException(hookId);
} }
} }
@Override @Override
@Cacheable(value = "hooks", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('HK')") @Cacheable(value = "hooks", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('HK')")
public List<Hook> retrieveHooksByEvent(final String actionName, public List<Hook> retrieveHooksByEvent(final String actionName,
final String entityName) { final String entityName) {
return this.hookRepository.findAllHooksListeningToEvent(actionName, return this.hookRepository.findAllHooksListeningToEvent(actionName,
entityName); entityName);
} }
@Override @Override
public HookData retrieveNewHookDetails(final String templateName) { public HookData retrieveNewHookDetails(final String templateName) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final TemplateMapper rm = new TemplateMapper(this.jdbcTemplate); final TemplateMapper rm = new TemplateMapper(this.jdbcTemplate);
final String sql; final String sql;
List<HookTemplateData> templateData; List<HookTemplateData> templateData;
if (templateName == null) { if (templateName == null) {
sql = "select " + rm.schema() + " order by s.name"; sql = "select " + rm.schema() + " order by s.name";
templateData = this.jdbcTemplate.query(sql, rm, new Object[] {}); templateData = this.jdbcTemplate.query(sql, rm, new Object[]{});
} else { } else {
sql = "select " + rm.schema() + " where s.name = ? order by s.name"; sql = "select " + rm.schema() + " where s.name = ? order by s.name";
templateData = this.jdbcTemplate.query(sql, rm, templateData = this.jdbcTemplate.query(sql, rm,
new Object[] { templateName }); new Object[]{templateName});
} }
final List<Grouping> events = getTemplateForEvents(); final List<Grouping> events = getTemplateForEvents();
return HookData.template(templateData, events); return HookData.template(templateData, events);
} }
private List<Grouping> getTemplateForEvents() { private List<Grouping> getTemplateForEvents() {
final String sql = "select p.grouping, p.entity_name, p.action_name from m_permission p " 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%' " + " where p.action_name NOT LIKE '%CHECKER%' AND p.action_name NOT LIKE '%READ%' "
+ " order by p.grouping, p.entity_name "; + " order by p.grouping, p.entity_name ";
final EventResultSetExtractor extractor = new EventResultSetExtractor(); final EventResultSetExtractor extractor = new EventResultSetExtractor();
return this.jdbcTemplate.query(sql, extractor); return this.jdbcTemplate.query(sql, extractor);
} }
private static final class HookMapper implements RowMapper<HookData> { private static final class HookMapper implements RowMapper<HookData> {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
public HookMapper(final JdbcTemplate jdbcTemplate) { public HookMapper(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
} }
public String schema() { public String schema() {
return " h.id, s.name as name, h.name as display_name, h.is_active, h.created_date, h.lastmodified_date " return " h.id, s.name as name, h.name as display_name, h.is_active, h.created_date,"
+ " from m_hook h inner join m_hook_templates s on h.template_id = s.id "; + " h.lastmodified_date, h.ugd_template_id, tp.name as ugd_template_name, "
} + "h.ugd_template_id from m_hook h inner join m_hook_templates s on h.template_id = s.id"
+ " inner join m_template tp on h.ugd_template_id = tp.id";
}
@Override @Override
public HookData mapRow(final ResultSet rs, public HookData mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum) @SuppressWarnings("unused") final int rowNum)
throws SQLException { throws SQLException {
final Long id = rs.getLong("id"); final Long id = rs.getLong("id");
final String name = rs.getString("name"); final String name = rs.getString("name");
final String displayname = rs.getString("display_name"); final String displayname = rs.getString("display_name");
final boolean isActive = rs.getBoolean("is_active"); final boolean isActive = rs.getBoolean("is_active");
final LocalDate createdAt = JdbcSupport.getLocalDate(rs, final LocalDate createdAt = JdbcSupport.getLocalDate(rs,
"created_date"); "created_date");
final LocalDate updatedAt = JdbcSupport.getLocalDate(rs, final LocalDate updatedAt = JdbcSupport.getLocalDate(rs,
"lastmodified_date"); "lastmodified_date");
final List<Event> registeredEvents = retrieveEvents(id); final Long templateId = rs.getLong("ugd_template_id");
final List<Field> config = retrieveConfig(id); final String templateName = rs.getString("ugd_template_name");
final List<Event> registeredEvents = retrieveEvents(id);
final List<Field> config = retrieveConfig(id);
return HookData.instance(id, name, displayname, isActive, return HookData.instance(id, name, displayname, isActive,
createdAt, updatedAt, registeredEvents, config); createdAt, updatedAt, templateId, registeredEvents, config,
} templateName);
}
private List<Event> retrieveEvents(final Long hookId) { private List<Event> retrieveEvents(final Long hookId) {
final HookEventMapper rm = new HookEventMapper(); final HookEventMapper rm = new HookEventMapper();
final String sql = "select " + rm.schema() + " where h.id= ?"; final String sql = "select " + rm.schema() + " where h.id= ?";
return this.jdbcTemplate.query(sql, rm, new Object[] { hookId }); return this.jdbcTemplate.query(sql, rm, new Object[]{hookId});
} }
private List<Field> retrieveConfig(final Long hookId) { private List<Field> retrieveConfig(final Long hookId) {
final HookConfigMapper rm = new HookConfigMapper(); final HookConfigMapper rm = new HookConfigMapper();
final String sql = "select " + rm.schema() final String sql = "select " + rm.schema()
+ " where h.id= ? order by hc.field_name"; + " where h.id= ? order by hc.field_name";
final List<Field> fields = this.jdbcTemplate.query(sql, rm, final List<Field> fields = this.jdbcTemplate.query(sql, rm,
new Object[] { hookId }); new Object[]{hookId});
return fields; return fields;
} }
} }
private static final class HookEventMapper implements RowMapper<Event> { private static final class HookEventMapper implements RowMapper<Event> {
public String schema() { 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 "; 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 @Override
public Event mapRow(final ResultSet rs, public Event mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum) @SuppressWarnings("unused") final int rowNum)
throws SQLException { throws SQLException {
final String actionName = rs.getString("action_name"); final String actionName = rs.getString("action_name");
final String entityName = rs.getString("entity_name"); final String entityName = rs.getString("entity_name");
return Event.instance(actionName, entityName); return Event.instance(actionName, entityName);
} }
} }
private static final class HookConfigMapper implements RowMapper<Field> { private static final class HookConfigMapper implements RowMapper<Field> {
public String schema() { 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 "; return " hc.field_name, hc.field_value from m_hook h inner join m_hook_configuration hc on h.id = hc.hook_id ";
} }
@Override @Override
public Field mapRow(final ResultSet rs, public Field mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum) @SuppressWarnings("unused") final int rowNum)
throws SQLException { throws SQLException {
final String fieldName = rs.getString("field_name"); final String fieldName = rs.getString("field_name");
final String fieldValue = rs.getString("field_value"); final String fieldValue = rs.getString("field_value");
return Field.fromConfig(fieldName, fieldValue); return Field.fromConfig(fieldName, fieldValue);
} }
} }
private static final class TemplateMapper implements private static final class TemplateMapper
RowMapper<HookTemplateData> { implements
RowMapper<HookTemplateData> {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
public TemplateMapper(final JdbcTemplate jdbcTemplate) { public TemplateMapper(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
} }
public String schema() { public String schema() {
return " s.id, s.name from m_hook_templates s "; return " s.id, s.name from m_hook_templates s ";
} }
@Override @Override
public HookTemplateData mapRow(final ResultSet rs, public HookTemplateData mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum) @SuppressWarnings("unused") final int rowNum)
throws SQLException { throws SQLException {
final Long id = rs.getLong("id"); final Long id = rs.getLong("id");
final String name = rs.getString("name"); final String name = rs.getString("name");
final List<Field> schema = retrieveSchema(id); final List<Field> schema = retrieveSchema(id);
return HookTemplateData.instance(id, name, schema); return HookTemplateData.instance(id, name, schema);
} }
private List<Field> retrieveSchema(final Long templateId) { private List<Field> retrieveSchema(final Long templateId) {
final TemplateSchemaMapper rm = new TemplateSchemaMapper(); final TemplateSchemaMapper rm = new TemplateSchemaMapper();
final String sql = "select " + rm.schema() final String sql = "select " + rm.schema()
+ " where s.id= ? order by hs.field_name "; + " where s.id= ? order by hs.field_name ";
final List<Field> fields = this.jdbcTemplate.query(sql, rm, final List<Field> fields = this.jdbcTemplate.query(sql, rm,
new Object[] { templateId }); new Object[]{templateId});
return fields; return fields;
} }
} }
private static final class TemplateSchemaMapper implements RowMapper<Field> { private static final class TemplateSchemaMapper implements RowMapper<Field> {
public String schema() { public String schema() {
return " hs.field_type, hs.field_name, hs.placeholder, hs.optional from m_hook_templates s " 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 "; + " inner join m_hook_schema hs on s.id = hs.hook_template_id ";
} }
@Override @Override
public Field mapRow(final ResultSet rs, public Field mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum) @SuppressWarnings("unused") final int rowNum)
throws SQLException { throws SQLException {
final String fieldName = rs.getString("field_name"); final String fieldName = rs.getString("field_name");
final String fieldType = rs.getString("field_type"); final String fieldType = rs.getString("field_type");
final Boolean optional = rs.getBoolean("optional"); final Boolean optional = rs.getBoolean("optional");
final String placeholder = rs.getString("placeholder"); final String placeholder = rs.getString("placeholder");
return Field return Field
.fromSchema(fieldType, fieldName, optional, placeholder); .fromSchema(fieldType, fieldName, optional, placeholder);
} }
} }
} }
@@ -5,22 +5,8 @@
*/ */
package org.mifosplatform.infrastructure.hooks.service; package org.mifosplatform.infrastructure.hooks.service;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.actionNameParamName; import com.google.gson.JsonArray;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.configParamName; import com.google.gson.JsonObject;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.contentTypeName;
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.payloadURLName;
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.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
@@ -29,313 +15,338 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.hooks.domain.Hook; import org.mifosplatform.infrastructure.hooks.domain.*;
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.HookNotFoundException;
import org.mifosplatform.infrastructure.hooks.exception.HookTemplateNotFoundException; import org.mifosplatform.infrastructure.hooks.exception.HookTemplateNotFoundException;
import org.mifosplatform.infrastructure.hooks.processor.ProcessorHelper; import org.mifosplatform.infrastructure.hooks.processor.ProcessorHelper;
import org.mifosplatform.infrastructure.hooks.processor.WebHookService; import org.mifosplatform.infrastructure.hooks.processor.WebHookService;
import org.mifosplatform.infrastructure.hooks.serialization.HookCommandFromApiJsonDeserializer; import org.mifosplatform.infrastructure.hooks.serialization.HookCommandFromApiJsonDeserializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.template.domain.Template;
import org.mifosplatform.template.domain.TemplateRepository;
import org.mifosplatform.template.exception.TemplateNotFoundException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import retrofit.RetrofitError; import retrofit.RetrofitError;
import com.google.gson.JsonArray; import java.util.*;
import com.google.gson.JsonObject; import java.util.Map.Entry;
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.*;
@Service @Service
public class HookWritePlatformServiceJpaRepositoryImpl implements public class HookWritePlatformServiceJpaRepositoryImpl
HookWritePlatformService { implements
HookWritePlatformService {
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final HookRepository hookRepository; private final HookRepository hookRepository;
private final HookTemplateRepository hookTemplateRepository; private final HookTemplateRepository hookTemplateRepository;
private final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final TemplateRepository ugdTemplateRepository;
private final FromJsonHelper fromApiJsonHelper; private final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final FromJsonHelper fromApiJsonHelper;
@Autowired @Autowired
public HookWritePlatformServiceJpaRepositoryImpl( public HookWritePlatformServiceJpaRepositoryImpl(
final PlatformSecurityContext context, final PlatformSecurityContext context,
final HookRepository hookRepository, final HookRepository hookRepository,
final HookTemplateRepository hookTemplateRepository, final HookTemplateRepository hookTemplateRepository,
final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer, final TemplateRepository ugdTemplateRepository,
final FromJsonHelper fromApiJsonHelper) { final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer,
this.context = context; final FromJsonHelper fromApiJsonHelper) {
this.hookRepository = hookRepository; this.context = context;
this.hookTemplateRepository = hookTemplateRepository; this.hookRepository = hookRepository;
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.hookTemplateRepository = hookTemplateRepository;
this.fromApiJsonHelper = fromApiJsonHelper; this.ugdTemplateRepository = ugdTemplateRepository;
} this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Transactional @Transactional
@Override @Override
@CacheEvict(value = "hooks", allEntries = true) @CacheEvict(value = "hooks", allEntries = true)
public CommandProcessingResult createHook(final JsonCommand command) { public CommandProcessingResult createHook(final JsonCommand command) {
try { try {
this.context.authenticatedUser(); this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForCreate(command.json()); this.fromApiJsonDeserializer.validateForCreate(command.json());
final HookTemplate template = retrieveHookTemplateBy(command final HookTemplate template = retrieveHookTemplateBy(command
.stringValueOfParameterNamed(nameParamName)); .stringValueOfParameterNamed(nameParamName));
final String configJson = command.jsonFragment(configParamName); final String configJson = command.jsonFragment(configParamName);
final Set<HookConfiguration> config = assembleConfig( final Set<HookConfiguration> config = assembleConfig(
command.mapValueOfParameterNamed(configJson), template); command.mapValueOfParameterNamed(configJson), template);
final JsonArray events = command final JsonArray events = command
.arrayOfParameterNamed(eventsParamName); .arrayOfParameterNamed(eventsParamName);
final Set<HookResource> allEvents = assembleSetOfEvents(events); final Set<HookResource> allEvents = assembleSetOfEvents(events);
final Hook hook = Hook.fromJson(command, template, config, Template ugdTemplate = null;
allEvents); if (command.hasParameter(templateIdParamName)) {
final Long ugdTemplateId = command
.longValueOfParameterNamed(templateIdParamName);
ugdTemplate = this.ugdTemplateRepository.findOne(ugdTemplateId);
if (ugdTemplate == null) {
throw new TemplateNotFoundException(ugdTemplateId);
}
}
final Hook hook = Hook.fromJson(command, template, config,
allEvents, ugdTemplate);
validateHookRules(template, config, allEvents); validateHookRules(template, config, allEvents);
this.hookRepository.save(hook); this.hookRepository.save(hook);
return new CommandProcessingResultBuilder() return new CommandProcessingResultBuilder()
.withCommandId(command.commandId()) .withCommandId(command.commandId())
.withEntityId(hook.getId()).build(); .withEntityId(hook.getId()).build();
} catch (final DataIntegrityViolationException dve) { } catch (final DataIntegrityViolationException dve) {
handleHookDataIntegrityIssues(command, dve); handleHookDataIntegrityIssues(command, dve);
return CommandProcessingResult.empty(); return CommandProcessingResult.empty();
} }
} }
@Transactional @Transactional
@Override @Override
@CacheEvict(value = "hooks", allEntries = true) @CacheEvict(value = "hooks", allEntries = true)
public CommandProcessingResult updateHook(final Long hookId, public CommandProcessingResult updateHook(final Long hookId,
final JsonCommand command) { final JsonCommand command) {
try { try {
this.context.authenticatedUser(); this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForUpdate(command.json()); this.fromApiJsonDeserializer.validateForUpdate(command.json());
final Hook hook = retrieveHookBy(hookId); final Hook hook = retrieveHookBy(hookId);
final HookTemplate template = hook.getHookTemplate(); final HookTemplate template = hook.getHookTemplate();
final Map<String, Object> changes = hook.update(command); final Map<String, Object> changes = hook.update(command);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
if (changes.containsKey(eventsParamName)) { if (changes.containsKey(templateIdParamName)) {
final Set<HookResource> events = assembleSetOfEvents(command final Long ugdTemplateId = command
.arrayOfParameterNamed(eventsParamName)); .longValueOfParameterNamed(templateIdParamName);
final boolean updated = hook.updateEvents(events); final Template ugdTemplate = this.ugdTemplateRepository
if (!updated) { .findOne(ugdTemplateId);
changes.remove(eventsParamName); if (ugdTemplate == null) {
} changes.remove(templateIdParamName);
} throw new TemplateNotFoundException(ugdTemplateId);
}
hook.updateUgdTemplate(ugdTemplate);
}
if (changes.containsKey(configParamName)) { if (changes.containsKey(eventsParamName)) {
final String configJson = command final Set<HookResource> events = assembleSetOfEvents(command
.jsonFragment(configParamName); .arrayOfParameterNamed(eventsParamName));
final Set<HookConfiguration> config = assembleConfig( final boolean updated = hook.updateEvents(events);
command.mapValueOfParameterNamed(configJson), if (!updated) {
template); changes.remove(eventsParamName);
final boolean updated = hook.updateConfig(config); }
if (!updated) { }
changes.remove(configParamName);
}
}
this.hookRepository.saveAndFlush(hook); 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);
}
}
return new CommandProcessingResultBuilder() // this.hookRepository.saveAndFlush(hook);
.withCommandId(command.commandId()) // }
.withEntityId(hookId) //
.with(changes) //
.build();
} catch (final DataIntegrityViolationException dve) {
handleHookDataIntegrityIssues(command, dve);
return null;
}
}
@Transactional return new CommandProcessingResultBuilder() //
@Override .withCommandId(command.commandId()) //
@CacheEvict(value = "hooks", allEntries = true) .withEntityId(hookId) //
public CommandProcessingResult deleteHook(final Long hookId) { .with(changes) //
.build();
} catch (final DataIntegrityViolationException dve) {
handleHookDataIntegrityIssues(command, dve);
return null;
}
}
this.context.authenticatedUser(); @Transactional
@Override
@CacheEvict(value = "hooks", allEntries = true)
public CommandProcessingResult deleteHook(final Long hookId) {
final Hook hook = retrieveHookBy(hookId); this.context.authenticatedUser();
try { final Hook hook = retrieveHookBy(hookId);
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) { try {
final Hook hook = this.hookRepository.findOne(hookId); this.hookRepository.delete(hook);
if (hook == null) { this.hookRepository.flush();
throw new HookNotFoundException(hookId.toString()); } catch (final DataIntegrityViolationException e) {
} throw new PlatformDataIntegrityException(
return hook; "error.msg.unknown.data.integrity.issue",
} "Unknown data integrity issue with resource: "
+ e.getMostSpecificCause());
}
return new CommandProcessingResultBuilder().withEntityId(hookId)
.build();
}
private HookTemplate retrieveHookTemplateBy(final String templateName) { private Hook retrieveHookBy(final Long hookId) {
final HookTemplate template = this.hookTemplateRepository final Hook hook = this.hookRepository.findOne(hookId);
.findOne(templateName); if (hook == null) {
if (template == null) { throw new HookNotFoundException(hookId.toString());
throw new HookTemplateNotFoundException(templateName); }
} return hook;
return template; }
}
private Set<HookConfiguration> assembleConfig( private HookTemplate retrieveHookTemplateBy(final String templateName) {
final Map<String, String> hookConfig, final HookTemplate template) { final HookTemplate template = this.hookTemplateRepository
.findOne(templateName);
if (template == null) {
throw new HookTemplateNotFoundException(templateName);
}
return template;
}
final Set<HookConfiguration> configuration = new HashSet<>(); private Set<HookConfiguration> assembleConfig(
final Set<Schema> fields = template.getSchema(); final Map<String, String> hookConfig, final HookTemplate template) {
for (final Entry<String, String> configEntry : hookConfig.entrySet()) { final Set<HookConfiguration> configuration = new HashSet<>();
for (final Schema field : fields) { final Set<Schema> fields = template.getSchema();
final String fieldName = field.getFieldName();
if (fieldName.equalsIgnoreCase(configEntry.getKey())) {
final HookConfiguration config = HookConfiguration for (final Entry<String, String> configEntry : hookConfig.entrySet()) {
.createNewWithoutHook(field.getFieldType(), for (final Schema field : fields) {
configEntry.getKey(), final String fieldName = field.getFieldName();
configEntry.getValue()); if (fieldName.equalsIgnoreCase(configEntry.getKey())) {
configuration.add(config);
break;
}
}
} final HookConfiguration config = HookConfiguration
.createNewWithoutHook(field.getFieldType(),
configEntry.getKey(),
configEntry.getValue());
configuration.add(config);
break;
}
}
return configuration; }
}
private Set<HookResource> assembleSetOfEvents(final JsonArray eventsArray) { return configuration;
}
final Set<HookResource> allEvents = new HashSet<>(); private Set<HookResource> assembleSetOfEvents(final JsonArray eventsArray) {
for (int i = 0; i < eventsArray.size(); i++) { final Set<HookResource> allEvents = new HashSet<>();
final JsonObject eventElement = eventsArray.get(i) for (int i = 0; i < eventsArray.size(); i++) {
.getAsJsonObject();
final String entityName = this.fromApiJsonHelper final JsonObject eventElement = eventsArray.get(i)
.extractStringNamed(entityNameParamName, eventElement); .getAsJsonObject();
final String actionName = this.fromApiJsonHelper
.extractStringNamed(actionNameParamName, eventElement);
final HookResource event = HookResource.createNewWithoutHook(
entityName, actionName);
allEvents.add(event);
}
return allEvents; 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);
}
private void validateHookRules(final HookTemplate template, return allEvents;
final Set<HookConfiguration> config, Set<HookResource> events) { }
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); private void validateHookRules(final HookTemplate template,
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( final Set<HookConfiguration> config, Set<HookResource> events) {
dataValidationErrors).resource("hook");
if (!template.getName().equalsIgnoreCase(webTemplateName) final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
&& this.hookRepository.findOneByTemplateId(template.getId()) != null) { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
final String errorMessage = "multiple.non.web.template.hooks.not.supported"; dataValidationErrors).resource("hook");
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
errorMessage);
}
for (final HookConfiguration conf : config) { if (!template.getName().equalsIgnoreCase(webTemplateName)
final String fieldValue = conf.getFieldValue(); && this.hookRepository.findOneByTemplateId(template.getId()) != null) {
if (conf.getFieldName().equals(contentTypeName)) { final String errorMessage = "multiple.non.web.template.hooks.not.supported";
if (!(fieldValue.equalsIgnoreCase("json") || fieldValue baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
.equalsIgnoreCase("form"))) { errorMessage);
final String errorMessage = "content.type.must.be.json.or.form"; }
baseDataValidator.reset()
.failWithCodeNoParameterAddedToErrorCode(
errorMessage);
}
}
if (conf.getFieldName().equals(payloadURLName)) { for (final HookConfiguration conf : config) {
try { final String fieldValue = conf.getFieldValue();
final WebHookService service = ProcessorHelper if (conf.getFieldName().equals(contentTypeName)) {
.createWebHookService(fieldValue); if (!(fieldValue.equalsIgnoreCase("json") || fieldValue
service.sendEmptyRequest(); .equalsIgnoreCase("form"))) {
} catch (RetrofitError re) { final String errorMessage = "content.type.must.be.json.or.form";
// Swallow error if it's because of method not supported or baseDataValidator.reset()
// if url throws 404 - required for integration test, .failWithCodeNoParameterAddedToErrorCode(
// url generated on 1st POST request errorMessage);
if (re.getResponse() == null) { }
String errorMessage = "url.invalid"; }
baseDataValidator.reset()
.failWithCodeNoParameterAddedToErrorCode(
errorMessage);
}
}
}
}
if (events == null || events.isEmpty()) { if (conf.getFieldName().equals(payloadURLName)) {
final String errorMessage = "registered.events.cannot.be.empty"; try {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( final WebHookService service = ProcessorHelper
errorMessage); .createWebHookService(fieldValue);
} service.sendEmptyRequest();
} catch (RetrofitError re) {
// Swallow error if it's because of method not supported or
// if url throws 404 - required for integration test,
// url generated on 1st POST request
if (re.getResponse() == null) {
String errorMessage = "url.invalid";
baseDataValidator.reset()
.failWithCodeNoParameterAddedToErrorCode(
errorMessage);
}
}
}
}
final Set<Schema> fields = template.getSchema(); if (events == null || events.isEmpty()) {
for (final Schema field : fields) { final String errorMessage = "registered.events.cannot.be.empty";
if (!field.isOptional()) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
boolean found = false; errorMessage);
for (final HookConfiguration conf : config) { }
if (field.getFieldName().equals(conf.getFieldName())) {
found = true;
}
}
if (!found) {
final String errorMessage = "required.config.field."
+ "not.provided";
baseDataValidator.reset().value(field.getFieldName())
.failWithCodeNoParameterAddedToErrorCode(
errorMessage);
}
}
}
if (!dataValidationErrors.isEmpty()) { final Set<Schema> fields = template.getSchema();
throw new PlatformApiDataValidationException(dataValidationErrors); 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."
+ "not.provided";
baseDataValidator
.reset()
.value(field.getFieldName())
.failWithCodeNoParameterAddedToErrorCode(
errorMessage);
}
}
}
private void handleHookDataIntegrityIssues(final JsonCommand command, if (!dataValidationErrors.isEmpty()) {
final DataIntegrityViolationException dve) { throw new PlatformApiDataValidationException(dataValidationErrors);
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( private void handleHookDataIntegrityIssues(final JsonCommand command,
"error.msg.unknown.data.integrity.issue", final DataIntegrityViolationException dve) {
"Unknown data integrity issue with resource: " final Throwable realCause = dve.getMostSpecificCause();
+ realCause.getMessage()); 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());
}
} }
@@ -28,7 +28,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
@Entity @Entity
@Table(name = "m_template", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") }) @Table(name = "m_template", uniqueConstraints = {@UniqueConstraint(columnNames = {"name"}, name = "unq_name")})
public class Template extends AbstractPersistable<Long> { public class Template extends AbstractPersistable<Long> {
@Column(name = "name", nullable = false, unique = true) @Column(name = "name", nullable = false, unique = true)
@@ -49,7 +49,8 @@ public class Template extends AbstractPersistable<Long> {
@OneToMany(targetEntity = TemplateMapper.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER) @OneToMany(targetEntity = TemplateMapper.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<TemplateMapper> mappers; private List<TemplateMapper> mappers;
public Template(final String name, final String text, final TemplateEntity entity, final TemplateType type, public Template(final String name, final String text,
final TemplateEntity entity, final TemplateType type,
final List<TemplateMapper> mappers) { final List<TemplateMapper> mappers) {
this.name = StringUtils.defaultIfEmpty(name, null); this.name = StringUtils.defaultIfEmpty(name, null);
this.entity = entity; this.entity = entity;
@@ -65,16 +66,29 @@ public class Template extends AbstractPersistable<Long> {
public static Template fromJson(final JsonCommand command) { public static Template fromJson(final JsonCommand command) {
final String name = command.stringValueOfParameterNamed("name"); final String name = command.stringValueOfParameterNamed("name");
final String text = command.stringValueOfParameterNamed("text"); final String text = command.stringValueOfParameterNamed("text");
final TemplateEntity entity = TemplateEntity.values()[command.integerValueSansLocaleOfParameterNamed("entity")]; final TemplateEntity entity = TemplateEntity.values()[command
final TemplateType type = TemplateType.values()[command.integerValueSansLocaleOfParameterNamed("type")]; .integerValueSansLocaleOfParameterNamed("entity")];
final int templateTypeId = command
.integerValueSansLocaleOfParameterNamed("type");
TemplateType type = null;
switch (templateTypeId) {
case 0 :
type = TemplateType.DOCUMENT;
break;
case 2 :
type = TemplateType.SMS;
break;
}
final JsonArray array = command.arrayOfParameterNamed("mappers"); final JsonArray array = command.arrayOfParameterNamed("mappers");
final List<TemplateMapper> mappersList = new ArrayList<>(); final List<TemplateMapper> mappersList = new ArrayList<>();
for (final JsonElement element : array) { for (final JsonElement element : array) {
mappersList.add(new TemplateMapper(element.getAsJsonObject().get("mappersorder").getAsInt(), element.getAsJsonObject() mappersList.add(new TemplateMapper(element.getAsJsonObject()
.get("mapperskey").getAsString(), element.getAsJsonObject().get("mappersvalue").getAsString())); .get("mappersorder").getAsInt(), element.getAsJsonObject()
.get("mapperskey").getAsString(), element.getAsJsonObject()
.get("mappersvalue").getAsString()));
} }
return new Template(name, text, entity, type, mappersList); return new Template(name, text, entity, type, mappersList);
@@ -13,11 +13,11 @@ import com.google.gson.annotations.SerializedName;
public enum TemplateType { public enum TemplateType {
@SerializedName("Document") @SerializedName("Document")
DOCUMENT(0, "Document"); DOCUMENT(0, "Document"), @SerializedName("SMS")
SMS(2, "SMS");
/** /**
* @SerializedName("E-Mail") EMAIL(1, "E-Mail"), * @SerializedName("E-Mail") EMAIL(1, "E-Mail")
* @SerializedName("SMS") SMS(2, "SMS");
*/ */
private int id; private int id;
private String name; private String name;
@@ -44,7 +44,9 @@ public class JpaTemplateDomainService implements TemplateDomainService {
@Override @Override
public Template findOneById(final Long id) { public Template findOneById(final Long id) {
final Template template = this.templateRepository.findOne(id); final Template template = this.templateRepository.findOne(id);
if (template == null) { throw new TemplateNotFoundException(id); } if (template == null) {
throw new TemplateNotFoundException(id);
}
return template; return template;
} }
@@ -58,12 +60,14 @@ public class JpaTemplateDomainService implements TemplateDomainService {
final Template template = Template.fromJson(command); final Template template = Template.fromJson(command);
this.templateRepository.saveAndFlush(template); this.templateRepository.saveAndFlush(template);
return new CommandProcessingResultBuilder().withEntityId(template.getId()).build(); return new CommandProcessingResultBuilder().withEntityId(
template.getId()).build();
} }
@Transactional @Transactional
@Override @Override
public CommandProcessingResult updateTemplate(final Long templateId, final JsonCommand command) { public CommandProcessingResult updateTemplate(final Long templateId,
final JsonCommand command) {
// FIXME - no validation here of the data in the command object, is // FIXME - no validation here of the data in the command object, is
// name, text populated etc // name, text populated etc
// FIXME - handle cases where data integrity constraints are fired from // FIXME - handle cases where data integrity constraints are fired from
@@ -72,20 +76,36 @@ public class JpaTemplateDomainService implements TemplateDomainService {
final Template template = findOneById(templateId); final Template template = findOneById(templateId);
template.setName(command.stringValueOfParameterNamed(PROPERTY_NAME)); template.setName(command.stringValueOfParameterNamed(PROPERTY_NAME));
template.setText(command.stringValueOfParameterNamed(PROPERTY_TEXT)); template.setText(command.stringValueOfParameterNamed(PROPERTY_TEXT));
template.setEntity(TemplateEntity.values()[command.integerValueSansLocaleOfParameterNamed(PROPERTY_ENTITY)]); template.setEntity(TemplateEntity.values()[command
template.setType(TemplateType.values()[command.integerValueSansLocaleOfParameterNamed(PROPERTY_TYPE)]); .integerValueSansLocaleOfParameterNamed(PROPERTY_ENTITY)]);
final int templateTypeId = command
.integerValueSansLocaleOfParameterNamed(PROPERTY_TYPE);
TemplateType type = null;
switch (templateTypeId) {
case 0 :
type = TemplateType.DOCUMENT;
break;
case 2 :
type = TemplateType.SMS;
break;
}
template.setType(type);
final JsonArray array = command.arrayOfParameterNamed("mappers"); final JsonArray array = command.arrayOfParameterNamed("mappers");
final List<TemplateMapper> mappersList = new ArrayList<>(); final List<TemplateMapper> mappersList = new ArrayList<>();
for (final JsonElement element : array) { for (final JsonElement element : array) {
mappersList.add(new TemplateMapper(element.getAsJsonObject().get("mappersorder").getAsInt(), element.getAsJsonObject() mappersList.add(new TemplateMapper(element.getAsJsonObject()
.get("mapperskey").getAsString(), element.getAsJsonObject().get("mappersvalue").getAsString())); .get("mappersorder").getAsInt(), element.getAsJsonObject()
.get("mapperskey").getAsString(), element.getAsJsonObject()
.get("mappersvalue").getAsString()));
} }
template.setMappers(mappersList); template.setMappers(mappersList);
this.templateRepository.saveAndFlush(template); this.templateRepository.saveAndFlush(template);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(template.getId()).build(); return new CommandProcessingResultBuilder()
.withCommandId(command.commandId())
.withEntityId(template.getId()).build();
} }
@Transactional @Transactional
@@ -95,7 +115,8 @@ public class JpaTemplateDomainService implements TemplateDomainService {
this.templateRepository.delete(template); this.templateRepository.delete(template);
return new CommandProcessingResultBuilder().withEntityId(templateId).build(); return new CommandProcessingResultBuilder().withEntityId(templateId)
.build();
} }
@Transactional @Transactional
@@ -105,7 +126,8 @@ public class JpaTemplateDomainService implements TemplateDomainService {
} }
@Override @Override
public List<Template> getAllByEntityAndType(final TemplateEntity entity, final TemplateType type) { public List<Template> getAllByEntityAndType(final TemplateEntity entity,
final TemplateType type) {
return this.templateRepository.findByEntityAndType(entity, type); return this.templateRepository.findByEntityAndType(entity, type);
} }
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectMapper;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.template.domain.Template; import org.mifosplatform.template.domain.Template;
import org.mifosplatform.template.domain.TemplateFunctions; import org.mifosplatform.template.domain.TemplateFunctions;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@@ -35,23 +36,32 @@ import com.github.mustachejava.MustacheFactory;
@Service @Service
public class TemplateMergeService { public class TemplateMergeService {
// private final FromJsonHelper fromApiJsonHelper; // private final FromJsonHelper fromApiJsonHelper;
private Map<String, Object> scopes; private Map<String, Object> scopes;
private String authToken;
// @Autowired // @Autowired
// public TemplateMergeService(final FromJsonHelper fromApiJsonHelper) { // public TemplateMergeService(final FromJsonHelper fromApiJsonHelper) {
// this.fromApiJsonHelper = fromApiJsonHelper; // this.fromApiJsonHelper = fromApiJsonHelper;
// } // }
public String compile(final Template template, final Map<String, Object> scopes) throws MalformedURLException, IOException { public void setAuthToken(final String authToken) {
this.authToken = authToken;
}
public String compile(final Template template,
final Map<String, Object> scopes) throws MalformedURLException,
IOException {
this.scopes = scopes; this.scopes = scopes;
this.scopes.put("static", new TemplateFunctions()); this.scopes.put("static", new TemplateFunctions());
final MustacheFactory mf = new DefaultMustacheFactory(); final MustacheFactory mf = new DefaultMustacheFactory();
final Mustache mustache = mf.compile(new StringReader(template.getText()), template.getName()); final Mustache mustache = mf.compile(
new StringReader(template.getText()), template.getName());
final Map<String, Object> mappers = getCompiledMapFromMappers(template.getMappersAsMap()); final Map<String, Object> mappers = getCompiledMapFromMappers(template
.getMappersAsMap());
this.scopes.putAll(mappers); this.scopes.putAll(mappers);
final StringWriter stringWriter = new StringWriter(); final StringWriter stringWriter = new StringWriter();
@@ -60,14 +70,16 @@ public class TemplateMergeService {
return stringWriter.toString(); return stringWriter.toString();
} }
private Map<String, Object> getCompiledMapFromMappers(final Map<String, String> data) { private Map<String, Object> getCompiledMapFromMappers(
final Map<String, String> data) {
final MustacheFactory mf = new DefaultMustacheFactory(); final MustacheFactory mf = new DefaultMustacheFactory();
if (data != null) { if (data != null) {
for (final Map.Entry<String, String> entry : data.entrySet()) { for (final Map.Entry<String, String> entry : data.entrySet()) {
final Mustache mappersMustache = mf.compile(new StringReader(entry.getValue()), ""); final Mustache mappersMustache = mf.compile(new StringReader(
entry.getValue()), "");
final StringWriter stringWriter = new StringWriter(); final StringWriter stringWriter = new StringWriter();
mappersMustache.execute(stringWriter, this.scopes); mappersMustache.execute(stringWriter, this.scopes);
@@ -88,11 +100,13 @@ public class TemplateMergeService {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Map<String, Object> getMapFromUrl(final String url) throws MalformedURLException, IOException { private Map<String, Object> getMapFromUrl(final String url)
throws MalformedURLException, IOException {
final HttpURLConnection connection = getConnection(url); final HttpURLConnection connection = getConnection(url);
final String response = getStringFromInputStream(connection.getInputStream()); final String response = getStringFromInputStream(connection
.getInputStream());
HashMap<String, Object> result = new HashMap<>(); HashMap<String, Object> result = new HashMap<>();
if (connection.getContentType().equals("text/plain")) { if (connection.getContentType().equals("text/plain")) {
result.put("src", response); result.put("src", response);
@@ -104,20 +118,29 @@ public class TemplateMergeService {
private HttpURLConnection getConnection(final String url) { private HttpURLConnection getConnection(final String url) {
final String name = SecurityContextHolder.getContext().getAuthentication().getName(); if (this.authToken == null) {
final String password = SecurityContextHolder.getContext().getAuthentication().getCredentials().toString(); final String name = SecurityContextHolder.getContext()
.getAuthentication().getName();
final String password = SecurityContextHolder.getContext()
.getAuthentication().getCredentials().toString();
Authenticator.setDefault(new Authenticator() { Authenticator.setDefault(new Authenticator() {
@Override @Override
protected PasswordAuthentication getPasswordAuthentication() { protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(name, password.toCharArray()); return new PasswordAuthentication(name, password
} .toCharArray());
}); }
});
}
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
connection = (HttpURLConnection) new URL(url).openConnection(); connection = (HttpURLConnection) new URL(url).openConnection();
if (this.authToken != null) {
connection.setRequestProperty("Authorization", "Basic "
+ this.authToken);
}
TrustModifier.relaxHostChecking(connection); TrustModifier.relaxHostChecking(connection);
connection.setDoInput(true); connection.setDoInput(true);
@@ -0,0 +1,2 @@
ALTER TABLE `m_hook` ADD COLUMN `ugd_template_id` bigint(20) NOT NULL;
ALTER TABLE `m_hook` ADD CONSTRAINT `fk_ugd_template_id` FOREIGN KEY (`ugd_template_id`) REFERENCES `m_template` (`id`);