All action executions now return object of type ActionExecutionResult. RestApiPlugin returns the same object which contains statusCode, headers and body.

This commit is contained in:
Trisha Anand 2019-09-30 18:17:35 +00:00
parent 4fcdd76588
commit 8658df95a9
19 changed files with 190 additions and 74 deletions

View File

@ -64,6 +64,12 @@
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@ -1,5 +1,6 @@
package com.appsmith.external.models;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@ -9,6 +10,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.http.HttpMethod;
import java.util.List;
import java.util.Map;
@Getter
@Setter
@ -29,7 +31,7 @@ public class ActionConfiguration {
String path;
List<Property> headers;
List<Property> queryParameters;
JSONObject body;
Map<String, Object> body;
HttpMethod httpMethod;
// DB action fields

View File

@ -0,0 +1,18 @@
package com.appsmith.external.models;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@NoArgsConstructor
public class ActionExecutionResult {
String statusCode;
JsonNode headers;
JsonNode body;
}

View File

@ -2,9 +2,11 @@ package com.appsmith.external.models;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Property {
String key;

View File

@ -21,4 +21,6 @@ public class ResourceConfiguration {
List<Property> properties;
//For REST API
List<Property> headers;
}

View File

@ -1,14 +1,15 @@
package com.appsmith.external.plugins;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.ResourceConfiguration;
import org.pf4j.ExtensionPoint;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
public interface PluginExecutor extends ExtensionPoint {
Flux<Object> execute(ResourceConfiguration resourceConfiguration, ActionConfiguration action, List<Param> params);
Mono<ActionExecutionResult> execute(ResourceConfiguration resourceConfiguration, ActionConfiguration action, List<Param> params);
}

View File

@ -1,6 +1,7 @@
package com.external.plugins;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.ResourceConfiguration;
import com.appsmith.external.plugins.BasePlugin;
@ -10,7 +11,7 @@ import org.pf4j.Extension;
import org.pf4j.PluginException;
import org.pf4j.PluginWrapper;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.sql.Connection;
import java.sql.DriverManager;
@ -73,9 +74,9 @@ public class PostgresPlugin extends BasePlugin {
public static class PostgresPluginExecutor implements PluginExecutor {
@Override
public Flux<Object> execute(ResourceConfiguration resourceConfiguration,
ActionConfiguration actionConfiguration,
List<Param> params) {
public Mono<ActionExecutionResult> execute(ResourceConfiguration resourceConfiguration,
ActionConfiguration actionConfiguration,
List<Param> params) {
log.debug("In the PostgresPlugin execute with resourceConfiguration: {}, ActionConfig: {}",
resourceConfiguration, actionConfiguration);
@ -98,9 +99,11 @@ public class PostgresPlugin extends BasePlugin {
} catch (SQLException e) {
log.error("", e);
}
//Return list because list is the actual result. ActionExecutionResult is just a stop gap measure
list.forEach(System.out::println);
return Flux.fromIterable(list);
ActionExecutionResult result = new ActionExecutionResult();
return Mono.just(result);
}
}

View File

@ -1,31 +1,37 @@
package com.external.plugins;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.ResourceConfiguration;
import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.PluginExecutor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.pf4j.Extension;
import org.pf4j.PluginWrapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class RestApiPlugin extends BasePlugin {
private static ObjectMapper objectMapper;
public RestApiPlugin(PluginWrapper wrapper) {
super(wrapper);
this.objectMapper = new ObjectMapper();
}
@Slf4j
@ -33,14 +39,14 @@ public class RestApiPlugin extends BasePlugin {
public static class RestApiPluginExecutor implements PluginExecutor {
@Override
public Flux<Object> execute(ResourceConfiguration resourceConfiguration,
ActionConfiguration actionConfiguration,
List<Param> params) {
JSONObject requestBody = actionConfiguration.getBody();
public Mono<ActionExecutionResult> execute(ResourceConfiguration resourceConfiguration,
ActionConfiguration actionConfiguration,
List<Param> params) {
Map<String, Object> requestBody = actionConfiguration.getBody();
if (requestBody == null) {
requestBody = new JSONObject();
requestBody = (Map<String, Object>) new HashMap<String, Object>();
}
//TODO: Substitue variables from params in all parts (actionConfig, resourceConfig etc) via JsonPath: https://github.com/json-path/JsonPath
Map<String, Param> propertyMap = params.stream()
.collect(Collectors.toMap(Param::getKey, param -> param));
@ -48,39 +54,51 @@ public class RestApiPlugin extends BasePlugin {
String url = resourceConfiguration.getUrl() + path;
HttpMethod httpMethod = actionConfiguration.getHttpMethod();
if (httpMethod == null) {
return Flux.error(new Exception("HttpMethod must not be null"));
return Mono.error(new Exception("HttpMethod must not be null"));
}
log.debug("Going to make a RestApi call to url: {}, httpMethod: {}", url, httpMethod);
WebClient.Builder webClientBuilder = WebClient.builder().baseUrl(url);
WebClient webClient = WebClient.builder()
.baseUrl(url)
// TODO: Ideally this should come from action / resource config
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
WebClient.RequestHeadersSpec<?> request = webClient.method(httpMethod)
.body(BodyInserters.fromObject(requestBody));
Mono<ClientResponse> responseMono = request.exchange();
return responseMono.flatMapMany(response -> {
log.debug("Got response: {}", response);
// TODO: Refactor for better switch case
List<String> contentTypes = response.headers().header(HttpHeaders.CONTENT_TYPE);
Class clazz = String.class;
if (contentTypes != null && contentTypes.size() > 0) {
String contentType = contentTypes.get(0);
boolean isJson = MediaType.APPLICATION_JSON_UTF8_VALUE.toLowerCase()
.equals(contentType.toLowerCase()
.replaceAll("\\s", ""))
|| MediaType.APPLICATION_JSON_VALUE.equals(contentType.toLowerCase());
if (isJson) {
clazz = JSONObject.class;
}
if (resourceConfiguration.getHeaders() != null) {
List<Property> headers = resourceConfiguration.getHeaders();
for (Property header : headers) {
webClientBuilder.defaultHeader(header.getKey(), header.getValue());
}
return response.bodyToFlux(clazz);
});
}
if (actionConfiguration.getHeaders() != null) {
List<Property> headers = actionConfiguration.getHeaders();
for (Property header : headers) {
webClientBuilder.defaultHeader(header.getKey(), header.getValue());
}
}
return webClientBuilder
.build()
.method(httpMethod)
.body(BodyInserters.fromObject(requestBody))
.exchange()
.flatMap(clientResponse -> clientResponse.toEntity(String.class))
.map(stringResponseEntity -> {
/**TODO
* Handle XML response. Currently we only handle JSON responses.
*/
HttpHeaders headers = stringResponseEntity.getHeaders();
String body = stringResponseEntity.getBody();
HttpStatus statusCode = stringResponseEntity.getStatusCode();
ActionExecutionResult result = new ActionExecutionResult();
result.setStatusCode(statusCode.toString());
try {
result.setBody(objectMapper.readTree(body));
String headerInJsonString = objectMapper.writeValueAsString(headers);
result.setHeaders(objectMapper.readTree(headerInJsonString));
} catch (IOException e) {
e.printStackTrace();
}
return result;
});
}
}
}

View File

@ -101,6 +101,11 @@
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>com.segment.analytics.java</groupId>
<artifactId>analytics</artifactId>

View File

@ -1,5 +1,6 @@
package com.appsmith.server.configurations;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
@ -31,4 +32,9 @@ public class CommonConfig {
public Validator validator() {
return Validation.buildDefaultValidatorFactory().getValidator();
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

View File

@ -1,5 +1,6 @@
package com.appsmith.server.controllers;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.server.constants.Url;
import com.appsmith.server.domains.Action;
import com.appsmith.server.dtos.ExecuteActionDTO;
@ -8,7 +9,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping(Url.ACTION_URL)
@ -19,7 +20,7 @@ public class ActionController extends BaseController<ActionService, Action, Stri
}
@PostMapping("/execute")
public Flux<Object> executeAction(@RequestBody ExecuteActionDTO executeActionDTO) {
public Mono<ActionExecutionResult> executeAction(@RequestBody ExecuteActionDTO executeActionDTO) {
return service.executeAction(executeActionDTO);
}
}

View File

@ -1,10 +1,11 @@
package com.appsmith.server.services;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.server.domains.Action;
import com.appsmith.server.dtos.ExecuteActionDTO;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ActionService extends CrudService<Action, String> {
Flux<Object> executeAction(ExecuteActionDTO executeActionDTO);
Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO);
}

View File

@ -1,5 +1,9 @@
package com.appsmith.server.services;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.ResourceConfiguration;
import com.appsmith.external.plugins.PluginExecutor;
import com.appsmith.server.domains.Action;
import com.appsmith.server.domains.Page;
@ -11,6 +15,10 @@ import com.appsmith.server.dtos.ExecuteActionDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.repositories.ActionRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.segment.analytics.Analytics;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginManager;
@ -18,14 +26,18 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
@ -36,6 +48,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
private final PluginService pluginService;
private final PageService pageService;
private final PluginManager pluginManager;
private final ObjectMapper objectMapper;
@Autowired
public ActionServiceImpl(Scheduler scheduler,
@ -48,13 +61,15 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
PageService pageService,
PluginManager pluginManager,
Analytics analytics,
SessionUserService sessionUserService) {
SessionUserService sessionUserService,
ObjectMapper objectMapper) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analytics, sessionUserService);
this.repository = repository;
this.resourceService = resourceService;
this.pluginService = pluginService;
this.pageService = pageService;
this.pluginManager = pluginManager;
this.objectMapper = objectMapper;
}
@Override
@ -108,7 +123,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
}
@Override
public Flux<Object> executeAction(ExecuteActionDTO executeActionDTO) {
public Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO) {
String actionId = executeActionDTO.getActionId();
log.debug("Going to execute action with id: {}", actionId);
@ -133,12 +148,62 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
);
// 3. Execute the query
return actionMono.flatMap(action -> resourceMono.zipWith(pluginExecutorMono, (resource, pluginExecutor) ->
{
log.debug("*** About to invoke the plugin**");
// TODO: The CommandParams is being passed as null here. Move it to interfaces.CommandParams - N/A
return pluginExecutor.execute(resource.getResourceConfiguration(), action.getActionConfiguration(), executeActionDTO.getParams());
}))
.flatMapIterable(Flux::toIterable);
return actionMono
.flatMap(action -> resourceMono.zipWith(pluginExecutorMono, (resource, pluginExecutor) -> {
log.debug("Variable substitutions before invoking the plugin");
//Do variable substitution before invoking the plugin
Map<String, String> replaceParamsMap = executeActionDTO
.getParams()
.stream()
.collect(Collectors.toMap(Param::getKey, Param::getValue,
// Incase there's a conflict, we pick the older value
(oldValue, newValue) -> oldValue)
);
ResourceConfiguration resourceConfiguration = (ResourceConfiguration) variableSubstitution(resource.getResourceConfiguration(), replaceParamsMap);
ActionConfiguration actionConfiguration = (ActionConfiguration) variableSubstitution(action.getActionConfiguration(), replaceParamsMap);
log.debug("About to invoke the plugin");
long start = System.currentTimeMillis();
Mono<ActionExecutionResult> actionExecutionResultMono = pluginExecutor.execute(resourceConfiguration, actionConfiguration, executeActionDTO.getParams());
long end = System.currentTimeMillis();
log.debug("Time taken by plugin executor is : {} ms",(end-start));
return actionExecutionResultMono;
}))
.flatMap(obj -> obj);
}
/**
* This function replaces the variables in the Object with the actual params
*/
public Object variableSubstitution(Object configuration,
Map<String, String> replaceParamsMap) {
try {
// Convert the object to String as a preparation to send it to mustacheReplacement
String objectInJsonString = objectMapper.writeValueAsString(configuration);
objectInJsonString = mustacheReplacement(objectInJsonString, configuration.getClass().getSimpleName(), replaceParamsMap);
return objectMapper.readValue(objectInJsonString, configuration.getClass());
} catch (Exception e) {
e.printStackTrace();
}
return configuration;
}
/**
*
* @param template : This is the string which contains {{key}} which would be replaced with value
* @param name : This is the class name of the object from which template string was created
* @param keyValueMap : This is the map of keys with values.
* @return It finally returns the string in which all the keys in template have been replaced with values.
*/
private String mustacheReplacement(String template, String name, Map<String, String> keyValueMap) {
MustacheFactory mf = new DefaultMustacheFactory();
Mustache mustache = mf.compile(new StringReader(template), name);
Writer writer = new StringWriter();
mustache.execute(writer, keyValueMap);
return writer.toString();
}
}

View File

@ -4,7 +4,6 @@ import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.OrganizationSetting;
import com.appsmith.server.domains.Setting;
import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.repositories.OrganizationRepository;

View File

@ -8,16 +8,13 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
@RunWith(SpringRunner.class)
@SpringBootTest

View File

@ -15,7 +15,6 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -23,7 +22,6 @@ import reactor.test.StepVerifier;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
@RunWith(SpringRunner.class)
@SpringBootTest

View File

@ -10,13 +10,11 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
@RunWith(SpringRunner.class)
@SpringBootTest

View File

@ -13,13 +13,11 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
@RunWith(SpringRunner.class)
@SpringBootTest

View File

@ -2,7 +2,6 @@ package com.appsmith.server.services;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.UserState;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import org.junit.Before;
@ -10,14 +9,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD;
@RunWith(SpringRunner.class)
@SpringBootTest