Skip to content

Commit

Permalink
Adding basic structure for plugin integrating with Redis (#1085)
Browse files Browse the repository at this point in the history
Co-authored-by: nitesh261193 <[email protected]>
  • Loading branch information
pc9795 and nitesh261193 authored Oct 22, 2020
1 parent a16372f commit bf69c7d
Show file tree
Hide file tree
Showing 6 changed files with 508 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
package com.appsmith.external.pluginExceptions;

public class StaleConnectionException extends RuntimeException {
public StaleConnectionException() {
}

public StaleConnectionException(String message) {
super(message);
}

public StaleConnectionException(String message, Throwable cause) {
super(message, cause);
}
}
5 changes: 2 additions & 3 deletions app/server/appsmith-plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
</parent>

<modelVersion>4.0.0</modelVersion>
<groupId>com.appsmith</groupId>
<artifactId>appsmith-plugins</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
Expand All @@ -22,6 +21,6 @@
<module>mysqlPlugin</module>
<module>elasticSearchPlugin</module>
<module>dynamoPlugin</module>
<module>redisPlugin</module>
</modules>

</project>
</project>
5 changes: 5 additions & 0 deletions app/server/appsmith-plugins/redisPlugin/plugin.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugin.id=redis-plugin
plugin.class=com.external.plugins.RedisPlugin
plugin.version=1.0-SNAPSHOT
plugin.provider[email protected]
plugin.dependencies=
115 changes: 115 additions & 0 deletions app/server/appsmith-plugins/redisPlugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.external.plugins</groupId>
<artifactId>redisPlugin</artifactId>
<version>1.0-SNAPSHOT</version>

<name>redisPlugin</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>redis-plugin</plugin.id>
<plugin.class>com.external.plugins.RedisPlugin</plugin.class>
<plugin.version>1.0-SNAPSHOT</plugin.version>
<plugin.provider>[email protected]</plugin.provider>
<plugin.dependencies/>
</properties>

<dependencies>

<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>0.6.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.appsmith</groupId>
<artifactId>interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>

<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.2.11.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.14.1</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<minimizeJar>false</minimizeJar>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<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>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package com.external.plugins;

import com.appsmith.external.models.*;
import com.appsmith.external.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.PluginExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.pf4j.Extension;
import org.pf4j.PluginWrapper;
import org.pf4j.util.StringUtils;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.util.SafeEncoder;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class RedisPlugin extends BasePlugin {
private static final Integer DEFAULT_PORT = 6379;

public RedisPlugin(PluginWrapper wrapper) {
super(wrapper);
}

@Slf4j
@Extension
public static class RedisPluginExecutor implements PluginExecutor<Jedis> {
@Override
public Mono<ActionExecutionResult> execute(Jedis jedis,
DatasourceConfiguration datasourceConfiguration,
ActionConfiguration actionConfiguration) {
String body = actionConfiguration.getBody();
if (StringUtils.isNullOrEmpty(body)) {
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR,
String.format("Body is null or empty [%s]", body)));
}

// First value will be the redis command and others are arguments for that command
String[] bodySplitted = body.trim().split("\\s+");

Protocol.Command command;
try {
// Commands are in upper case
command = Protocol.Command.valueOf(bodySplitted[0].toUpperCase());
} catch (IllegalArgumentException exc) {
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR,
String.format("Not a valid Redis command:%s", bodySplitted[0])));
}

Object commandOutput;
if (bodySplitted.length > 1) {
commandOutput = jedis.sendCommand(command, Arrays.copyOfRange(bodySplitted, 1, bodySplitted.length));
} else {
commandOutput = jedis.sendCommand(command);
}

ActionExecutionResult actionExecutionResult = new ActionExecutionResult();
actionExecutionResult.setBody(processCommandOutput(commandOutput));

return Mono.just(actionExecutionResult);
}

// This will be updated as we encounter different outputs.
private String processCommandOutput(Object commandOutput) {
if (commandOutput == null) {
return "null";
} else if (commandOutput instanceof byte[]) {
return SafeEncoder.encode((byte[]) commandOutput);
} else {
return String.valueOf(commandOutput);
}
}

@Override
public Mono<Jedis> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
if (datasourceConfiguration.getEndpoints().isEmpty()) {
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "No endpoint(s) configured"));
}

Endpoint endpoint = datasourceConfiguration.getEndpoints().get(0);
Integer port = (int) (long) ObjectUtils.defaultIfNull(endpoint.getPort(), DEFAULT_PORT);
Jedis jedis = new Jedis(endpoint.getHost(), port);

AuthenticationDTO auth = datasourceConfiguration.getAuthentication();
if (auth != null && AuthenticationDTO.Type.USERNAME_PASSWORD.equals(auth.getAuthType())) {
jedis.auth(auth.getUsername(), auth.getPassword());
}

return Mono.just(jedis);
}

@Override
public void datasourceDestroy(Jedis jedis) {
try {
if (jedis != null) {
jedis.close();
}
} catch (JedisConnectionException exc) {
log.error("Error closing Redis connection");
}
}

@Override
public Set<String> validateDatasource(DatasourceConfiguration datasourceConfiguration) {
Set<String> invalids = new HashSet<>();

if (CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) {
invalids.add("Missing endpoint(s)");
} else {
Endpoint endpoint = datasourceConfiguration.getEndpoints().get(0);
if (StringUtils.isNullOrEmpty(endpoint.getHost())) {
invalids.add("Missing host for endpoint");
}
}

AuthenticationDTO auth = datasourceConfiguration.getAuthentication();
if (auth != null && AuthenticationDTO.Type.USERNAME_PASSWORD.equals(auth.getAuthType())) {
if (StringUtils.isNullOrEmpty(datasourceConfiguration.getAuthentication().getUsername())) {
invalids.add("Missing username for authentication.");
}

if (StringUtils.isNullOrEmpty(datasourceConfiguration.getAuthentication().getPassword())) {
invalids.add("Missing password for authentication.");
}
}

return invalids;
}

private Mono<Void> verifyPing(Jedis jedis) {
String pingResponse;
try {
pingResponse = jedis.ping();
} catch (Exception exc) {
return Mono.error(exc);
}

if (!"PONG".equals(pingResponse)) {
return Mono.error(new RuntimeException(
String.format("Expected PONG in response of PING but got %s", pingResponse)));
}

return Mono.empty();
}

@Override
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
return datasourceCreate(datasourceConfiguration).
map(jedis -> {
verifyPing(jedis).block();
datasourceDestroy(jedis);
return new DatasourceTestResult();
}).
onErrorResume(error -> Mono.just(new DatasourceTestResult(error.getMessage())));
}

}
}
Loading

0 comments on commit bf69c7d

Please sign in to comment.