Adding mustache template substitution to QueryObj.

Now we can query postgres DB on the fly! Yipee!
This commit is contained in:
Arpit Mohan 2019-03-31 21:22:06 +05:30
parent 28ef85b893
commit a82a931093
8 changed files with 174 additions and 24 deletions

View File

@ -60,17 +60,24 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
<version>0.9.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -3,10 +3,8 @@ package com.mobtools.server.controllers;
import com.mobtools.server.constants.Url;
import com.mobtools.server.domains.Query;
import com.mobtools.server.dtos.CommandQueryParams;
import com.mobtools.server.dtos.ResponseDto;
import com.mobtools.server.services.QueryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@ -21,7 +19,6 @@ public class QueryController extends BaseController<QueryService, Query, String>
@PostMapping("/execute/{id}")
public Flux<Object> executeQuery(@PathVariable String id, @RequestBody CommandQueryParams params) {
return service.executeQuery(id, params)
.map(result -> new ResponseDto<>(HttpStatus.OK.value(), result, null));
return service.executeQuery(id, params);
}
}

View File

@ -1,8 +1,78 @@
package com.mobtools.server.services;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.mobtools.server.domains.Query;
import com.mobtools.server.dtos.CommandQueryParams;
import com.mobtools.server.dtos.Param;
import reactor.core.publisher.Flux;
public interface PluginExecutor {
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
Flux<Object> execute();
public abstract class PluginExecutor {
/**
* This function executes the command against a backend datasource.
* All the variables in commandTemplate of the Query object has already been replaced with actual values
* by the #replaceTemplate() function
*
* @param query
* @param params
* @return Flux<Object>
*/
abstract Flux<Object> execute(Query query, CommandQueryParams params);
/**
* This function should be run when the plugin is initialized
*/
abstract void init();
/**
* This function should be run when the plugin is destroyed
*/
abstract void destroy();
/**
* This function replaces the variables in the query commandTemplate with the actual params
* Executors can override this function to provide their own implementation if they wish to do something custom
*
* @param query Query
* @param params CommandQueryParams
*/
Query replaceTemplate(Query query, CommandQueryParams params) {
MustacheFactory mf = new DefaultMustacheFactory();
Mustache mustache = mf.compile(new StringReader(query.getCommandTemplate()), "commandTemplate");
Writer writer = new StringWriter();
Map<String, String> queryMap = new HashMap<>();
Map<String, String> headerMap = new HashMap<>();
if(params.getQueryParams() != null) {
queryMap = params
.getQueryParams()
.stream()
.collect(
Collectors.toMap(Param::getKey, Param::getValue,
// Incase there's a conflict, we pick the older value
(oldValue, newValue) -> oldValue));
}
if(params.getHeaderParams() != null) {
headerMap = params
.getHeaderParams()
.stream()
.collect(
Collectors.toMap(Param::getKey, Param::getValue,
// Incase there's a conflict, we pick the older value
(oldValue, newValue) -> oldValue));
}
mustache.execute(writer, queryMap);
query.setCommandTemplate(writer.toString());
return query;
}
}

View File

@ -1,25 +1,81 @@
package com.mobtools.server.services;
import com.mobtools.server.domains.Property;
import com.mobtools.server.domains.Query;
import com.mobtools.server.dtos.CommandQueryParams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
@Slf4j
@Component
public class PostgresDBPluginExecutor implements PluginExecutor {
public class PostgresDBPluginExecutor extends PluginExecutor {
// JDBC driver name and database URL
@Value("${jdbc.postgres.driver}")
static final String JDBC_DRIVER = "org.postgresql.Driver";
@Value("${jdbc.postgres.url}")
static final String DB_URL = "jdbc:postgresql://localhost/mobtools";
// Database credentials
@Value("${jdbc.postgres.username}")
static final String DB_USER = "postgres";
@Value("${jdbc.postgres.password}")
static final String DB_PASS = "root";
Connection conn = null;
@Override
public Flux<Object> execute() {
Property prop = new Property();
prop.setKey("name");
prop.setValue("val");
public Flux<Object> execute(Query queryObj, CommandQueryParams params) {
if(conn == null) {
init();
}
ArrayList list = new ArrayList(50);
try {
Statement statement = conn.createStatement();
String queryTemplate = queryObj.getCommandTemplate();
List<Property> props= new ArrayList<>();
props.add(prop);
ResultSet resultSet = statement.executeQuery(queryTemplate);
ResultSetMetaData metaData = resultSet.getMetaData();
Integer colCount = metaData.getColumnCount();
while(resultSet.next()) {
HashMap row = new HashMap(colCount);
for(int i = 1; i<=colCount; i++) {
row.put(metaData.getColumnName(i), resultSet.getObject(i));
}
list.add(row);
}
} catch (SQLException e) {
e.printStackTrace();
}
return Flux.fromIterable(list);
}
@Override
public void init() {
log.debug("Going to initialize the PostgresDBPluginExecutor");
try {
// Load the class into JVM
Class.forName(JDBC_DRIVER);
// Create the connection
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void destroy() {
return Flux.just(props);
}
}

View File

@ -4,8 +4,6 @@ import com.mobtools.server.domains.Query;
import com.mobtools.server.dtos.CommandQueryParams;
import reactor.core.publisher.Flux;
import java.util.stream.DoubleStream;
public interface QueryService extends CrudService<Query, String> {
Flux<Object> executeQuery(String id, CommandQueryParams params);

View File

@ -35,12 +35,20 @@ public class QueryServiceImpl extends BaseService<QueryRepository, Query, String
log.debug("Going to execute query with id: {}", id);
// 1. Fetch the query from the DB to get the type
return repository.findById(id)
.switchIfEmpty(Mono.defer(() -> Mono.error(new MobtoolsException("Unable to find query by id: " + id))))
// 2. Instantiate the implementation class based on the query type
.map(queryObj -> pluginService.getPluginExecutor(queryObj.getPlugin().getType(), queryObj.getPlugin().getExecutorClass()))
// 3. Execute the query
.flatMapMany(plugin -> plugin.execute());
Mono<Query> queryMono = repository.findById(id)
.switchIfEmpty(Mono.defer(() -> Mono.error(new MobtoolsException("Unable to find query by id: " + id))));
// 2. Instantiate the implementation class based on the query type
Mono<PluginExecutor> pluginExecutorMono = queryMono.map(queryObj ->
pluginService.getPluginExecutor(queryObj.getPlugin().getType(), queryObj.getPlugin().getExecutorClass()));
// 3. Execute the query
return queryMono
.zipWith(pluginExecutorMono, (queryObj, pluginExecutor) -> {
Query newQueryObj = pluginExecutor.replaceTemplate(queryObj, params);
return pluginExecutor.execute(newQueryObj, params);
})
.flatMapIterable(Flux::toIterable).subscribeOn(scheduler);
}
}

View File

@ -4,3 +4,11 @@ spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
#spring.data.mongodb.username=
#spring.data.mongodb.password=
logging.level.com.mobtools=debug
# JDBC Postgres properties
jdbc.postgres.driver=org.postgresql.Driver
jdbc.postgres.url=jdbc:postgresql://localhost/mobtools
jdbc.postgres.username=postgres
jdbc.postgres.password=root

View File

@ -2,3 +2,9 @@
spring.data.mongodb.database=mobtools
spring.data.mongodb.uri=mongodb+srv://admin:Y9PuxM52gcP3Dgfo@mobtools-test-cluster-swrsq.mongodb.net/mobtools?retryWrites=true
spring.data.mongodb.authentication-database=admin
# JDBC Postgres properties
jdbc.postgres.driver=org.postgresql.Driver
jdbc.postgres.url=jdbc:postgresql://ec2-54-247-85-251.eu-west-1.compute.amazonaws.com/d266aalso50024
jdbc.postgres.username=pornlzmggggpgk
jdbc.postgres.password=09275163cd7e737baf4c210b5e8db8ed88ddb7a0ee9acc82416fd75346ea4bbb