Mongo Plugin works here. Barring the scenarios where the query json contains special characters in keys ($, .) it works properly.

This commit is contained in:
Trisha Anand 2019-10-18 08:27:19 +00:00
parent 0473e3d48e
commit 84837fe57f
10 changed files with 268 additions and 12 deletions

View File

@ -35,7 +35,16 @@ public class ActionConfiguration {
HttpMethod httpMethod;
// DB action fields
String query;
/*
* For SQL plugins, the query field would be of the following format :
* {
* "cmd" : "select * from users;"
* }
* For noSQL plugins, the query json would be constructed according to
* the requirements of the plugin.
*/
Map<String, Object> query;
/*
* Future plugins could require more fields that are not covered above.

View File

@ -23,4 +23,9 @@ public class ResourceConfiguration {
//For REST API
List<Property> headers;
//This field is for plugins which allow the database name to be specified outside of the connection URL. The
//expectation from the plugins is that if the database name has been provided via this field, the database name
//should be according to this, else if database name is null, pick the database name from the URL.
String databaseName;
}

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.external.plugins</groupId>
<artifactId>mongoPlugin</artifactId>
<version>1.0-SNAPSHOT</version>
<name>mongoPlugin</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<plugin.id>mongo-plugin</plugin.id>
<plugin.class>com.external.plugins.MongoPlugin</plugin.class>
<plugin.version>1.0-SNAPSHOT</plugin.version>
<plugin.provider>tech@appsmith.com</plugin.provider>
<plugin.dependencies/>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>com.appsmith</groupId>
<artifactId>interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.11.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,130 @@
package com.external.plugins;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
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 com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.client.MongoDatabase;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pf4j.Extension;
import org.pf4j.PluginWrapper;
import reactor.core.publisher.Mono;
import java.math.BigInteger;
@Slf4j
public class MongoPlugin extends BasePlugin {
private static ObjectMapper objectMapper;
public MongoPlugin(PluginWrapper wrapper) {
super(wrapper);
this.objectMapper = new ObjectMapper();
}
@Slf4j
@Extension
public static class MongoPluginExecutor implements PluginExecutor {
/**
* For reference on creating the json queries for Mongo please head to
* https://docs.huihoo.com/mongodb/3.4/reference/command/index.html
*
* @param connection : This is the connection that is established to the data source. This connection is according
* to the parameters in Resource Configuration
* @param resourceConfiguration : These are the configurations which have been used to create a Resource from a Plugin
* @param actionConfiguration : These are the configurations which have been used to create an Action from a Resource.
* @return
*/
@Override
public Mono<ActionExecutionResult> execute(Object connection,
ResourceConfiguration resourceConfiguration,
ActionConfiguration actionConfiguration) {
ActionExecutionResult result = new ActionExecutionResult();
MongoClient mongoClient = (MongoClient) connection;
if (mongoClient == null) {
return Mono.error(new Exception("Mongo Client is null."));
}
MongoClientURI mongoClientURI= new MongoClientURI(resourceConfiguration.getUrl());
String databaseName = resourceConfiguration.getDatabaseName();
if (databaseName == null) {
databaseName = mongoClientURI.getDatabase();
}
MongoDatabase database = mongoClient.getDatabase(databaseName);
Bson command = new Document(actionConfiguration.getQuery());
try {
Document mongoOutput = database.runCommand(command);
JSONObject outputJson = new JSONObject(mongoOutput.toJson());
//The output json contains the key "ok". This is the status of the command
BigInteger status = outputJson.getBigInteger("ok");
JSONArray headerArray = new JSONArray();
if (BigInteger.ONE == status) {
//The json contains key "cursor" when find command was issued and there are 1 or more results. In case
//there are no results for find, this key is not present in the result json.
if (outputJson.has("cursor")) {
JSONArray outputResult = outputJson.getJSONObject("cursor").getJSONArray("firstBatch");
result.setBody(objectMapper.readTree(outputResult.toString()));
}
//The json contains key "n" when insert/update command is issued. "n" for update signifies the no of
//documents selected for update. "n" in case of insert signifies the number of documents inserted.
if (outputJson.has("n")) {
JSONObject body = new JSONObject().put("n", outputJson.getBigInteger("n"));
headerArray.put(body);
}
//The json key constains key "nModified" in case of update command. This signifies the no of
//documents updated.
if (outputJson.has("nModified")) {
JSONObject body = new JSONObject().put("nModified", outputJson.getBigInteger("nModified"));
headerArray.put(body);
}
/** TODO
* Go through all the possible fields that are returned in the output JSON and add all the fields
* that are important to the headerArray.
*/
}
JSONObject statusJson = new JSONObject().put("ok", status);
headerArray.put(statusJson);
result.setHeaders(objectMapper.readTree(headerArray.toString()));
} catch (Exception e) {
return Mono.error(e);
}
return Mono.just(result);
}
@Override
public Object resourceCreate(ResourceConfiguration resourceConfiguration) {
MongoClientURI mongoClientURI= new MongoClientURI(resourceConfiguration.getUrl());
return new MongoClient(mongoClientURI);
}
@Override
public void resourceDestroy(Object connection) {
MongoClient mongoClient = (MongoClient) connection;
if (mongoClient != null) {
mongoClient.close();
}
}
}
}

View File

@ -0,0 +1,18 @@
package com.external.plugins;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* Unit test for simple App.
*/
public class MongoPluginTest {
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue() {
assertTrue(true);
}
}

View File

@ -17,6 +17,7 @@
<modules>
<module>postgresPlugin</module>
<module>restApiPlugin</module>
<module>mongoPlugin</module>
</modules>
</project>

View File

@ -2,14 +2,11 @@ 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;
import com.appsmith.external.plugins.PluginExecutor;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.pf4j.Extension;
import org.pf4j.PluginException;
import org.pf4j.PluginWrapper;
@ -24,7 +21,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class PostgresPlugin extends BasePlugin {
@ -38,6 +35,13 @@ public class PostgresPlugin extends BasePlugin {
this.objectMapper = new ObjectMapper();
}
/**
* Postgres plugin receives the query as json of the following format :
* {
* "cmd" : "select * from users;"
* }
*/
@Slf4j
@Extension
public static class PostgresPluginExecutor implements PluginExecutor {
@ -53,8 +57,9 @@ public class PostgresPlugin extends BasePlugin {
ArrayList list = new ArrayList(50);
try {
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(actionConfiguration.getQuery());
Map<String, Object> queryJson = actionConfiguration.getQuery();
String query = (String) queryJson.get("cmd");
ResultSet resultSet = statement.executeQuery(query);
ResultSetMetaData metaData = resultSet.getMetaData();
Integer colCount = metaData.getColumnCount();
while (resultSet.next()) {
@ -90,7 +95,8 @@ public class PostgresPlugin extends BasePlugin {
} catch (SQLException e) {
log.error("", e);
}
return conn;
// Connection wasn't created. Return null
return null;
}
@Override

View File

@ -18,7 +18,8 @@ public enum AppsmithError {
UNAUTHORIZED_ACCESS(401, 4002, "Unauthorized access"),
INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request"),
REPOSITORY_SAVE_FAILED(500, 5001, "Repository save failed."),
PLUGIN_INSTALLATION_FAILED_DOWNLOAD_ERROR(500, 5002, "Due to error in downloading the plugin from remote repository, plugin installation has failed. Check the jar location and try again.");
PLUGIN_INSTALLATION_FAILED_DOWNLOAD_ERROR(500, 5002, "Due to error in downloading the plugin from remote repository, plugin installation has failed. Check the jar location and try again."),
PLUGIN_RUN_FAILED(500, 5003, "Plugin execution failed with error {0}");
private Integer httpErrorCode;

View File

@ -10,7 +10,6 @@ import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.PageAction;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.domains.Resource;
import com.appsmith.server.domains.ResourceContext;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.ExecuteActionDTO;
import com.appsmith.server.exceptions.AppsmithError;
@ -171,15 +170,15 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
resourceConfiguration = resource.getResourceConfiguration();
actionConfiguration = action.getActionConfiguration();
}
return resourceContextService
.getResourceContext(resource.getId())
//Now that we have the context (connection details, execute the action
//Now that we have the context (connection details, execute the action
.flatMap(resourceContext -> pluginExecutor.execute(
resourceContext.getConnection(),
resourceConfiguration,
actionConfiguration));
}))
.onErrorResume(e -> Mono.error(new AppsmithException(AppsmithError.PLUGIN_RUN_FAILED, e.getMessage())))
.flatMap(obj -> obj);
}

View File

@ -13,6 +13,9 @@ url_allow = true {
p = op.permission
}
# This is a global list of all the routes for all controllers. Any new controller that is written must
# carry an entry in this array. OPA performs ACL based on an intersection of these entries and permissions
# for a user + permissions inherited via the groups that the user is a part of.
allowed_operations = [
{"method": "POST", "resource": "users", "permission": "create:users"},
{"method": "GET", "resource": "users", "permission": "read:users"},