Hooks - Validation and Caching
This commit is contained in:
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.integrationtests;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.apache.http.conn.HttpHostConnectException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mifosplatform.integrationtests.common.HookHelper;
|
||||
import org.mifosplatform.integrationtests.common.OfficeHelper;
|
||||
import org.mifosplatform.integrationtests.common.Utils;
|
||||
|
||||
import com.jayway.restassured.RestAssured;
|
||||
import com.jayway.restassured.builder.RequestSpecBuilder;
|
||||
import com.jayway.restassured.builder.ResponseSpecBuilder;
|
||||
import com.jayway.restassured.http.ContentType;
|
||||
import com.jayway.restassured.path.json.JsonPath;
|
||||
import com.jayway.restassured.specification.RequestSpecification;
|
||||
import com.jayway.restassured.specification.ResponseSpecification;
|
||||
|
||||
public class HookIntegrationTest {
|
||||
|
||||
private RequestSpecification requestSpec;
|
||||
private ResponseSpecification responseSpec;
|
||||
|
||||
private HookHelper hookHelper;
|
||||
private OfficeHelper officeHelper;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Utils.initializeRESTAssured();
|
||||
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
this.hookHelper = new HookHelper(this.requestSpec, this.responseSpec);
|
||||
this.officeHelper = new OfficeHelper(this.requestSpec, this.responseSpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSendOfficeCreationNotification() {
|
||||
//Subject to https://echo-webhook.herokuapp.com being up
|
||||
//See http://www.jamesward.com/2014/06/11/testing-webhooks-was-a-pain-so-i-fixed-the-glitch
|
||||
final String payloadURL = "http://echo-webhook.herokuapp.com:80/Z7RXoCBdLSFMDrpn?";
|
||||
this.hookHelper.createHook(payloadURL);
|
||||
final Integer createdOfficeID = this.officeHelper.createOffice("01 January 2012");
|
||||
try {
|
||||
final String json = RestAssured.get(payloadURL.replace("?", "")).asString();
|
||||
final Integer notificationOfficeId = JsonPath.with(json).get("officeId");
|
||||
assertEquals("Equality check for created officeId and hook received payload officeId",
|
||||
createdOfficeID, notificationOfficeId);
|
||||
System.out.println("Notification Office Id - " + notificationOfficeId);
|
||||
} catch (final Exception e) {
|
||||
if (e instanceof HttpHostConnectException) {
|
||||
fail("Failed to connect to https://echo-webhook.herokuapp.com platform");
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.integrationtests.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.jayway.restassured.specification.RequestSpecification;
|
||||
import com.jayway.restassured.specification.ResponseSpecification;
|
||||
|
||||
public class HookHelper {
|
||||
|
||||
private final RequestSpecification requestSpec;
|
||||
private final ResponseSpecification responseSpec;
|
||||
|
||||
private static final String CREATE_HOOK_URL = "/mifosng-provider/api/v1/hooks?" + Utils.TENANT_IDENTIFIER;
|
||||
|
||||
public HookHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
|
||||
this.requestSpec = requestSpec;
|
||||
this.responseSpec = responseSpec;
|
||||
}
|
||||
|
||||
public Integer createHook(final String payloadURL) {
|
||||
System.out.println("---------------------------------CREATING A HOOK---------------------------------------------");
|
||||
return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOOK_URL, getTestHookAsJson(payloadURL),
|
||||
"resourceId");
|
||||
}
|
||||
|
||||
public String getTestHookAsJson(final String payloadURL) {
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("name", "Web");
|
||||
map.put("displayName", Utils.randomNameGenerator("Hook_DisplayName_", 5));
|
||||
map.put("isActive", "true");
|
||||
final HashMap<String, String> config = new HashMap<>();
|
||||
config.put("Content Type", "json");
|
||||
config.put("Payload URL", payloadURL);
|
||||
map.put("config", config);
|
||||
final ArrayList<HashMap<String, String>> events = new ArrayList<>();
|
||||
final HashMap<String, String> createOfficeEvent = new HashMap<>();
|
||||
createOfficeEvent.put("actionName", "CREATE");
|
||||
createOfficeEvent.put("entityName", "OFFICE");
|
||||
events.add(createOfficeEvent);
|
||||
map.put("events", events);
|
||||
System.out.println("map : " + map);
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -41,6 +41,7 @@ public class WebXmlConfiguration {
|
||||
jerseyServletRegistration.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
|
||||
jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters",
|
||||
ResponseCorsFilter.class.getName());
|
||||
jerseyServletRegistration.addInitParameter("com.sun.jersey.config.feature.DisableWADL", "true");
|
||||
// debugging for development:
|
||||
// jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters",
|
||||
// LoggingFilter.class.getName());
|
||||
|
||||
+3
-1
@@ -65,8 +65,10 @@ public class Hook extends AbstractAuditableCustom<AppUser, Long> {
|
||||
final Set<HookResource> events) {
|
||||
final String displayName = command
|
||||
.stringValueOfParameterNamed(displayNameParamName);
|
||||
final Boolean isActive = command
|
||||
Boolean isActive = command
|
||||
.booleanObjectValueOfParameterNamed(isActiveParamName);
|
||||
if(isActive == null)
|
||||
isActive = false;
|
||||
return new Hook(template, displayName, isActive, config, events);
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -10,11 +10,11 @@ import java.util.List;
|
||||
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
|
||||
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.HookRepository;
|
||||
import org.mifosplatform.infrastructure.hooks.event.HookEvent;
|
||||
import org.mifosplatform.infrastructure.hooks.event.HookEventSource;
|
||||
import org.mifosplatform.infrastructure.hooks.processor.HookProcessor;
|
||||
import org.mifosplatform.infrastructure.hooks.processor.HookProcessorProvider;
|
||||
import org.mifosplatform.infrastructure.hooks.service.HookReadPlatformService;
|
||||
import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -24,14 +24,14 @@ import org.springframework.stereotype.Service;
|
||||
public class MifosHookListener implements HookListener {
|
||||
|
||||
private final HookProcessorProvider hookProcessorProvider;
|
||||
private final HookRepository hookRepository;
|
||||
private final HookReadPlatformService hookReadPlatformService;
|
||||
private final TenantDetailsService tenantDetailsService;
|
||||
|
||||
@Autowired
|
||||
public MifosHookListener(final HookProcessorProvider hookProcessorProvider,
|
||||
final HookRepository hookRepository,
|
||||
final HookReadPlatformService hookReadPlatformService,
|
||||
final TenantDetailsService tenantDetailsService) {
|
||||
this.hookRepository = hookRepository;
|
||||
this.hookReadPlatformService = hookReadPlatformService;
|
||||
this.hookProcessorProvider = hookProcessorProvider;
|
||||
this.tenantDetailsService = tenantDetailsService;
|
||||
}
|
||||
@@ -51,10 +51,11 @@ public class MifosHookListener implements HookListener {
|
||||
final String entityName = hookEventSource.getEntityName();
|
||||
final String actionName = hookEventSource.getActionName();
|
||||
final String payload = event.getPayload();
|
||||
|
||||
final List<Hook> hooks = this.hookReadPlatformService
|
||||
.retrieveHooksByEvent(hookEventSource.getEntityName(),
|
||||
hookEventSource.getActionName());
|
||||
|
||||
final List<Hook> hooks = this.hookRepository
|
||||
.findAllHooksListeningToEvent(hookEventSource.getEntityName(),
|
||||
hookEventSource.getActionName());
|
||||
for (final Hook hook : hooks) {
|
||||
final HookProcessor processor = this.hookProcessorProvider
|
||||
.getProcessor(hook);
|
||||
|
||||
+1
-3
@@ -57,7 +57,7 @@ public class WebHookProcessor implements HookProcessor {
|
||||
private void sendRequest(final String url, final String contentType,
|
||||
final String payload, final String entityName,
|
||||
final String actionName, final String tenantIdentifier,
|
||||
final String authToken) {
|
||||
@SuppressWarnings("unused") final String authToken) {
|
||||
|
||||
final String mifosEndpointUrl = System.getProperty("baseUrl");
|
||||
final WebHookService service = ProcessorHelper
|
||||
@@ -70,13 +70,11 @@ public class WebHookProcessor implements HookProcessor {
|
||||
|| contentType.contains("json")) {
|
||||
final JsonObject json = new JsonParser().parse(payload)
|
||||
.getAsJsonObject();
|
||||
json.addProperty("mifosToken", authToken);
|
||||
service.sendJsonRequest(entityName, actionName, tenantIdentifier,
|
||||
mifosEndpointUrl, json, callback);
|
||||
} else {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map = new Gson().fromJson(payload, map.getClass());
|
||||
map.put("mifosToken", authToken);
|
||||
service.sendFormRequest(entityName, actionName, tenantIdentifier,
|
||||
mifosEndpointUrl, map, callback);
|
||||
}
|
||||
|
||||
+5
@@ -14,6 +14,7 @@ import retrofit.client.Response;
|
||||
import retrofit.http.Body;
|
||||
import retrofit.http.FieldMap;
|
||||
import retrofit.http.FormUrlEncoded;
|
||||
import retrofit.http.GET;
|
||||
import retrofit.http.Header;
|
||||
import retrofit.http.POST;
|
||||
|
||||
@@ -27,6 +28,10 @@ public interface WebHookService {
|
||||
final static String ENDPOINT_HEADER = "X-Mifos-Endpoint";
|
||||
final static String API_KEY_HEADER = "X-Mifos-API-Key";
|
||||
|
||||
// Ping
|
||||
@GET("/")
|
||||
Response sendEmptyRequest();
|
||||
|
||||
// Template - Web
|
||||
@POST("/")
|
||||
void sendJsonRequest(@Header(ENTITY_HEADER) String entityHeader,
|
||||
|
||||
+5
-1
@@ -6,8 +6,10 @@
|
||||
package org.mifosplatform.infrastructure.hooks.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.infrastructure.hooks.data.HookData;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Hook;
|
||||
|
||||
public interface HookReadPlatformService {
|
||||
|
||||
@@ -15,5 +17,7 @@ public interface HookReadPlatformService {
|
||||
|
||||
HookData retrieveHook(Long hookId);
|
||||
|
||||
HookData retrieveNewHookDetails(String templateName);
|
||||
List<Hook> retrieveHooksByEvent(final String actionName, final String entityName);
|
||||
|
||||
HookData retrieveNewHookDetails(String templateName);
|
||||
}
|
||||
|
||||
+15
@@ -19,9 +19,12 @@ 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.HookRepository;
|
||||
import org.mifosplatform.infrastructure.hooks.exception.HookNotFoundException;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
@@ -31,12 +34,15 @@ import org.springframework.stereotype.Service;
|
||||
public class HookReadPlatformServiceImpl implements HookReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final HookRepository hookRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@Autowired
|
||||
public HookReadPlatformServiceImpl(final PlatformSecurityContext context,
|
||||
final HookRepository hookRepository,
|
||||
final RoutingDataSource dataSource) {
|
||||
this.context = context;
|
||||
this.hookRepository = hookRepository;
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@@ -64,6 +70,15 @@ public class HookReadPlatformServiceImpl implements HookReadPlatformService {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "hooks", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('HK')")
|
||||
public List<Hook> retrieveHooksByEvent(final String actionName,
|
||||
final String entityName) {
|
||||
|
||||
return this.hookRepository.findAllHooksListeningToEvent(actionName,
|
||||
entityName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HookData retrieveNewHookDetails(final String templateName) {
|
||||
|
||||
|
||||
+54
-5
@@ -7,9 +7,11 @@ package org.mifosplatform.infrastructure.hooks.service;
|
||||
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.actionNameParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.configParamName;
|
||||
import static org.mifosplatform.infrastructure.hooks.api.HookApiConstants.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;
|
||||
@@ -36,13 +38,18 @@ import org.mifosplatform.infrastructure.hooks.domain.HookTemplateRepository;
|
||||
import org.mifosplatform.infrastructure.hooks.domain.Schema;
|
||||
import org.mifosplatform.infrastructure.hooks.exception.HookNotFoundException;
|
||||
import org.mifosplatform.infrastructure.hooks.exception.HookTemplateNotFoundException;
|
||||
import org.mifosplatform.infrastructure.hooks.processor.ProcessorHelper;
|
||||
import org.mifosplatform.infrastructure.hooks.processor.WebHookService;
|
||||
import org.mifosplatform.infrastructure.hooks.serialization.HookCommandFromApiJsonDeserializer;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import retrofit.RetrofitError;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@@ -72,6 +79,7 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@CacheEvict(value = "hooks", allEntries = true)
|
||||
public CommandProcessingResult createHook(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
@@ -90,7 +98,7 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
final Hook hook = Hook.fromJson(command, template, config,
|
||||
allEvents);
|
||||
|
||||
validateHookRules(template, config);
|
||||
validateHookRules(template, config, allEvents);
|
||||
|
||||
this.hookRepository.save(hook);
|
||||
|
||||
@@ -105,6 +113,7 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@CacheEvict(value = "hooks", allEntries = true)
|
||||
public CommandProcessingResult updateHook(final Long hookId,
|
||||
final JsonCommand command) {
|
||||
|
||||
@@ -156,6 +165,7 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@CacheEvict(value = "hooks", allEntries = true)
|
||||
public CommandProcessingResult deleteHook(final Long hookId) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
@@ -200,7 +210,9 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
|
||||
for (final Entry<String, String> configEntry : hookConfig.entrySet()) {
|
||||
for (final Schema field : fields) {
|
||||
if (field.getFieldName().equalsIgnoreCase(configEntry.getKey())) {
|
||||
final String fieldName = field.getFieldName();
|
||||
if (fieldName.equalsIgnoreCase(configEntry.getKey())) {
|
||||
|
||||
final HookConfiguration config = HookConfiguration
|
||||
.createNewWithoutHook(field.getFieldType(),
|
||||
configEntry.getKey(),
|
||||
@@ -237,7 +249,7 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
}
|
||||
|
||||
private void validateHookRules(final HookTemplate template,
|
||||
final Set<HookConfiguration> config) {
|
||||
final Set<HookConfiguration> config, Set<HookResource> events) {
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
|
||||
@@ -250,6 +262,43 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
for (final HookConfiguration conf : config) {
|
||||
final String fieldValue = conf.getFieldValue();
|
||||
if (conf.getFieldName().equals(contentTypeName)) {
|
||||
if (!(fieldValue.equalsIgnoreCase("json") || fieldValue
|
||||
.equalsIgnoreCase("form"))) {
|
||||
final String errorMessage = "content.type.must.be.json.or.form";
|
||||
baseDataValidator.reset()
|
||||
.failWithCodeNoParameterAddedToErrorCode(
|
||||
errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (conf.getFieldName().equals(payloadURLName)) {
|
||||
try {
|
||||
final WebHookService service = ProcessorHelper
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (events == null || events.isEmpty()) {
|
||||
final String errorMessage = "registered.events.cannot.be.empty";
|
||||
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
final Set<Schema> fields = template.getSchema();
|
||||
for (final Schema field : fields) {
|
||||
if (!field.isOptional()) {
|
||||
@@ -261,8 +310,8 @@ public class HookWritePlatformServiceJpaRepositoryImpl implements
|
||||
}
|
||||
if (!found) {
|
||||
final String errorMessage = "required.config.field."
|
||||
+ field.getFieldName() + ".not.provided";
|
||||
baseDataValidator.reset()
|
||||
+ "not.provided";
|
||||
baseDataValidator.reset().value(field.getFieldName())
|
||||
.failWithCodeNoParameterAddedToErrorCode(
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
@@ -34,4 +34,6 @@
|
||||
overflowToDisk="false" />
|
||||
<cache name="codes" maxEntriesLocalHeap="10000" eternal="true"
|
||||
overflowToDisk="false" />
|
||||
<cache name="hooks" maxEntriesLocalHeap="10000" eternal="true"
|
||||
overflowToDisk="false" />
|
||||
</ehcache>
|
||||
Reference in New Issue
Block a user