diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java index b7eee2ac34..6c5b2c5a97 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java @@ -30,14 +30,25 @@ import com.external.plugins.utils.MongoErrorUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.mongodb.DBObjectCodecProvider; +import com.mongodb.DBRefCodecProvider; import com.mongodb.MongoCommandException; import com.mongodb.MongoSocketWriteException; import com.mongodb.MongoTimeoutException; +import com.mongodb.client.gridfs.codecs.GridFSFileCodecProvider; +import com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider; import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoDatabase; import lombok.extern.slf4j.Slf4j; import org.bson.Document; +import org.bson.codecs.BsonTypeClassMap; +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.DocumentCodec; +import org.bson.codecs.DocumentCodecProvider; +import org.bson.codecs.ValueCodecProvider; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; import org.json.JSONArray; import org.json.JSONObject; @@ -98,6 +109,7 @@ import static com.external.plugins.constants.FieldName.UPDATE_OPERATION; import static com.external.plugins.constants.FieldName.UPDATE_QUERY; import static com.external.plugins.utils.MongoPluginUtils.urlEncode; import static java.lang.Boolean.TRUE; +import static java.util.Arrays.asList; import static org.apache.logging.log4j.util.Strings.isBlank; public class MongoPlugin extends BasePlugin { @@ -178,7 +190,7 @@ public class MongoPlugin extends BasePlugin { private static final Integer MONGO_COMMAND_EXCEPTION_UNAUTHORIZED_ERROR_CODE = 13; - private static final Set bsonFields = new HashSet<>(Arrays.asList( + private static final Set bsonFields = new HashSet<>(asList( AGGREGATE_PIPELINES, COUNT_QUERY, DELETE_QUERY, @@ -193,6 +205,18 @@ public class MongoPlugin extends BasePlugin { private static final MongoErrorUtils mongoErrorUtils = MongoErrorUtils.getInstance(); + private static final CodecRegistry DEFAULT_REGISTRY = CodecRegistries.fromProviders( + asList(new ValueCodecProvider(), + new BsonValueCodecProvider(), + new DocumentCodecProvider(), + new DBRefCodecProvider(), + new DBObjectCodecProvider(), + new BsonValueCodecProvider(), + new GeoJsonCodecProvider(), + new GridFSFileCodecProvider())); + + private static final BsonTypeClassMap DEFAULT_BSON_TYPE_CLASS_MAP = new org.bson.codecs.BsonTypeClassMap(); + public MongoPlugin(PluginWrapper wrapper) { super(wrapper); } @@ -332,7 +356,17 @@ public class MongoPlugin extends BasePlugin { ) .flatMap(mongoOutput -> { try { - JSONObject outputJson = new JSONObject(mongoOutput.toJson()); + /* + * Added Custom codec for JSON conversion since MongoDB Reactive API does not support + * processing of DbRef Object. + * https://github.com/spring-projects/spring-data-mongodb/issues/3015 : Mark Paluch commented + */ + DocumentCodec documentCodec = new DocumentCodec( + DEFAULT_REGISTRY, + DEFAULT_BSON_TYPE_CLASS_MAP + ); + + JSONObject outputJson = new JSONObject(mongoOutput.toJson(documentCodec)); //The output json contains the key "ok". This is the status of the command BigInteger status = outputJson.getBigInteger("ok"); diff --git a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java index cbe14a77d4..fc5a336b20 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java @@ -17,7 +17,9 @@ import com.appsmith.external.models.ParsedDataType; import com.appsmith.external.models.Property; import com.appsmith.external.models.RequestParamDTO; import com.appsmith.external.models.SSLDetails; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.mongodb.MongoCommandException; @@ -26,6 +28,7 @@ import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoCollection; import com.mongodb.reactivestreams.client.MongoDatabase; +import com.mongodb.DBRef; import org.bson.Document; import org.bson.types.BSONTimestamp; import org.bson.types.Decimal128; @@ -135,8 +138,25 @@ public class MongoPluginTest { )), new Document(Map.of("name", "Kierra Gentry", "gender", "F", "age", 40)) ))).block(); - } + + final MongoCollection addressCollection = mongoClient.getDatabase("test") + .getCollection("address"); + Mono.from(addressCollection.insertMany(List.of( + new Document(Map.of( + "user", new DBRef("test", "users", "1"), + "street", "First Street", + "city", "Line One", + "state", "UP" + )), + new Document(Map.of( + "user", new DBRef("AAA", "BBB", "2000"), + "street", "Second Street", + "city", "Line Two", + "state", "UP" + )) + ))).block(); + } return Mono.empty(); }).block(); } @@ -249,6 +269,55 @@ public class MongoPluginTest { .verifyComplete(); } + /** + * Test for DBRef after codec implementation + */ + @Test + public void testExecuteQueryDBRef() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + + Map configMap = new HashMap<>(); + setValueSafelyInFormData(configMap, SMART_SUBSTITUTION, Boolean.TRUE); + setValueSafelyInFormData(configMap, COMMAND, "RAW"); + setValueSafelyInFormData(configMap, BODY, "{\n" + + " find: \"address\",\n" + + " limit: 10,\n" + + " }"); + actionConfiguration.setFormData(configMap); + + Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), dsConfig, actionConfiguration)); + + StepVerifier.create(executeMono) + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + + assertEquals(2, ((ArrayNode) result.getBody()).size()); + + /* + * Provided Input : new DBRef("test", "users", "1") + * To test if we are getting the expected output after external codec implementation. + * Note: when the codec is removed from the MongoDBPlugin, this is found failing + */ + try { + ObjectMapper mapper = new ObjectMapper(); + String expectedOutputJsonString = "{\"$db\":\"test\",\"$ref\":\"users\",\"$id\":\"1\"}"; + JsonNode outputNode = mapper.readTree(expectedOutputJsonString); + assertEquals(outputNode, (((ArrayNode) result.getBody()).findValue("user"))); + } catch (JsonProcessingException e) { + assert false; + } + + }) + .verifyComplete(); + } + + @Test public void testExecuteReadQuery() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); @@ -475,8 +544,13 @@ public class MongoPluginTest { StepVerifier.create(structureMono) .assertNext(structure -> { + //Sort the Tables since one more table is added and to maintain sequence + structure.getTables().sort( + (DatasourceStructure.Table t1, DatasourceStructure.Table t2) + ->t2.getName().compareTo(t1.getName()) + ); assertNotNull(structure); - assertEquals(1, structure.getTables().size()); + assertEquals(2, structure.getTables().size()); final DatasourceStructure.Table usersTable = structure.getTables().get(0); assertEquals("users", usersTable.getName());