From fbada3051d193efd827110a9f18de07bd815f968 Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Tue, 4 Feb 2020 12:02:51 +0000 Subject: [PATCH] Adding default implementation in BaseRepositoryImpl for default JPA queries defined by Spring Data. We override the SimpleReactiveMongoRepository with our custom implementation to add criteria for filtering soft deleted records. Also, adding a new function to archive record instead of a hard delete. --- .../server/configurations/CommonConfig.java | 6 - .../server/configurations/MongoConfig.java | 44 +++---- .../appsmith/server/constants/FieldName.java | 1 + .../server/repositories/ActionRepository.java | 5 +- .../server/repositories/BaseRepository.java | 26 ++++ .../repositories/BaseRepositoryImpl.java | 114 ++++++++++++++++++ .../server/services/ActionService.java | 7 +- .../server/services/ActionServiceImpl.java | 10 -- .../services/ApplicationServiceImpl.java | 41 ++++++- 9 files changed, 196 insertions(+), 58 deletions(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java index 8fbdbd94c0..9e5c99544a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java @@ -1,6 +1,5 @@ package com.appsmith.server.configurations; -import com.appsmith.server.configurations.mongo.SoftDeleteMongoRepositoryFactoryBean; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,8 +8,6 @@ import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.config.EnableMongoAuditing; -import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; @@ -21,9 +18,6 @@ import java.util.List; @Getter @Setter @Configuration -@EnableMongoAuditing -@EnableReactiveMongoRepositories(repositoryFactoryBeanClass = SoftDeleteMongoRepositoryFactoryBean.class, - basePackages = "com.appsmith.server.repositories") public class CommonConfig { private String ELASTIC_THREAD_POOL_NAME = "appsmith-elastic-pool"; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java index f7fccfc72f..d696bb5322 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java @@ -1,31 +1,23 @@ package com.appsmith.server.configurations; -import com.mongodb.reactivestreams.client.MongoClient; -import com.mongodb.reactivestreams.client.MongoClients; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import com.appsmith.server.configurations.mongo.SoftDeleteMongoRepositoryFactoryBean; +import com.appsmith.server.repositories.BaseRepositoryImpl; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.config.EnableMongoAuditing; import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; -@EnableReactiveMongoRepositories -public class MongoConfig extends AbstractReactiveMongoConfiguration { - - @Value("${spring.data.mongodb.database}") - private String dbName; - - @Override - public MongoClient reactiveMongoClient() { - return MongoClients.create(); - } - - @Bean - public ReactiveMongoTemplate reactiveMongoTemplate() throws Exception { - return new ReactiveMongoTemplate(reactiveMongoClient(), dbName); - } - - @Override - protected String getDatabaseName() { - return dbName; - } +/** + * This configures the JPA Mongo repositories. The default base implementation is defined in {@link BaseRepositoryImpl}. + * This is required to add default clauses for default JPA queries defined by Spring Data. + * + * The factoryBean class is also custom defined in order to add default clauses for soft delete for all custom JPA queries. + * {@link SoftDeleteMongoRepositoryFactoryBean} for details. + */ +@Configuration +@EnableMongoAuditing +@EnableReactiveMongoRepositories(repositoryFactoryBeanClass = SoftDeleteMongoRepositoryFactoryBean.class, + basePackages = "com.appsmith.server.repositories", + repositoryBaseClass = BaseRepositoryImpl.class +) +public class MongoConfig { } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java index 26b0c3f2f9..a294079482 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java @@ -3,6 +3,7 @@ package com.appsmith.server.constants; public class FieldName { public static final String EMAIL = "email"; public static final String ORGANIZATION_ID = "organizationId"; + public static final String DELETED = "deleted"; public static String ORGANIZATION = "organization"; public static String ID = "id"; public static String NAME = "name"; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java index 7723421e4d..6d62775cae 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java @@ -15,10 +15,7 @@ public interface ActionRepository extends BaseRepository { Mono findByNameAndPageId(String name, String pageId); - Flux findDistinctActionsByNameInAndPageId(Set names, String pageId); + Flux findByPageId(String pageId); Flux findDistinctActionsByNameInAndPageIdAndActionConfiguration_HttpMethod(Set names, String pageId, String httpMethod); - - Flux saveAll(List actions); - } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepository.java index cf4cb1b4a3..eef3852672 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepository.java @@ -2,10 +2,36 @@ package com.appsmith.server.repositories; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.data.repository.NoRepositoryBean; +import reactor.core.publisher.Mono; import java.io.Serializable; +import java.util.List; @NoRepositoryBean public interface BaseRepository extends ReactiveMongoRepository { + /** + * This function sets the deleted flag to true and then saves the modified document. + * + * @param T The entity which needs to be archived + * @return Mono + */ + Mono archiveById(T entity); + + /** + * This function directly updates the document by setting the deleted flag to true for the entity with the given id + * + * @param id The id of the document that needs to be archived + * @return + */ + Mono archiveById(ID id); + + /** + * This function directly updates the DB by setting the deleted flag to true for all the documents in the collection + * with the given list of ids. + * + * @param ids The list of ids of the document that needs to be archived. + * @return + */ + Mono archiveAllById(List ids); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java new file mode 100644 index 0000000000..16336d0654 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java @@ -0,0 +1,114 @@ +package com.appsmith.server.repositories; + +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.BaseDomain; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.mongodb.core.ReactiveMongoOperations; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.data.mongodb.repository.query.MongoEntityInformation; +import org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository; +import org.springframework.util.Assert; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.io.Serializable; +import java.util.List; + +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * This repository implementation is the base class that will be used by Spring Data running all the default JPA queries. + * We override the default implementation {@link SimpleReactiveMongoRepository} to filter out records marked with + * deleted=true. + * To enable this base implementation, it MUST be set in the annotation @EnableReactiveMongoRepositories.repositoryBaseClass. + * This is currently defined in {@link com.appsmith.server.configurations.MongoConfig} (liable to change in the future). + *

+ * An implementation like this can also be used to set default query parameters based on the user's role and permissions + * to filter out data that they are allowed to see. This is will be implemented with ACL. + * + * @param The domain class that extends {@link BaseDomain}. This is required because we use default fields in + * {@link BaseDomain} such as `deleted` + * @param The ID field that extends Serializable interface + */ +@Slf4j +public class BaseRepositoryImpl extends SimpleReactiveMongoRepository + implements BaseRepository { + + private final MongoEntityInformation entityInformation; + private final ReactiveMongoOperations mongoOperations; + + public BaseRepositoryImpl(@NonNull MongoEntityInformation entityInformation, + @NonNull ReactiveMongoOperations mongoOperations) { + super(entityInformation, mongoOperations); + this.entityInformation = entityInformation; + this.mongoOperations = mongoOperations; + } + + private Criteria notDeleted() { + return new Criteria().orOperator( + where("deleted").exists(false), + where("deleted").is(false) + ); + } + + private Criteria getIdCriteria(Object id) { + return where(entityInformation.getIdAttribute()).is(id); + } + + @Override + public Mono findById(ID id) { + Assert.notNull(id, "The given id must not be null!"); + Query query = new Query(getIdCriteria(id)); + query.addCriteria(notDeleted()); + return mongoOperations.query(entityInformation.getJavaType()) + .inCollection(entityInformation.getCollectionName()) + .matching(query) + .one(); + } + + @Override + public Flux findAll() { + Query query = new Query(notDeleted()); + return mongoOperations.find(query, entityInformation.getJavaType(), entityInformation.getCollectionName()); + } + + @Override + public Mono archiveById(T entity) { + Assert.notNull(entity, "The given entity must not be null!"); + Assert.notNull(entity.getId(), "The given entity's id must not be null!"); + Assert.isTrue(!entity.getDeleted(), "The given entity is already deleted"); + + entity.setDeleted(true); + return mongoOperations.save(entity, entityInformation.getCollectionName()); + } + + @Override + public Mono archiveById(ID id) { + Assert.notNull(id, "The given id must not be null!"); + Query query = new Query(getIdCriteria(id)); + query.addCriteria(notDeleted()); + + Update update = new Update(); + update.set(FieldName.DELETED, true); + return mongoOperations.updateFirst(query, update, entityInformation.getJavaType()) + .map(result -> result.getModifiedCount() > 0 ? true : false); + } + + @Override + public Mono archiveAllById(List ids) { + Assert.notNull(ids, "The given ids must not be null!"); + Assert.notEmpty(ids, "The given list of ids must not be empty!"); + + Query query = new Query(); + query.addCriteria(new Criteria().where(FieldName.ID).in(ids)); + query.addCriteria(notDeleted()); + + Update update = new Update(); + update.set(FieldName.DELETED, true); + return mongoOperations.updateMulti(query, update, entityInformation.getJavaType()) + .map(result -> result.getModifiedCount() > 0 ? true : false); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionService.java index 4fd9c6915e..981716ba4b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionService.java @@ -6,7 +6,6 @@ import com.appsmith.server.dtos.ExecuteActionDTO; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.List; import java.util.Set; public interface ActionService extends CrudService { @@ -17,11 +16,7 @@ public interface ActionService extends CrudService { Mono findByNameAndPageId(String name, String pageId); - Flux findDistinctActionsByNameInAndPageId(Set names, String pageId); - Flux findDistinctRestApiActionsByNameInAndPageIdAndHttpMethod(Set names, String pageId, String httpMethod); - Flux saveAll(List actions); - - public Action extractAndSetJsonPathKeys(Action action); + Action extractAndSetJsonPathKeys(Action action); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java index 05a8227514..eb7cf2a044 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java @@ -451,21 +451,11 @@ public class ActionServiceImpl extends BaseService findDistinctActionsByNameInAndPageId(Set names, String pageId) { - return repository.findDistinctActionsByNameInAndPageId(names, pageId); - } - @Override public Flux findDistinctRestApiActionsByNameInAndPageIdAndHttpMethod(Set names, String pageId, String httpMethod) { return repository.findDistinctActionsByNameInAndPageIdAndActionConfiguration_HttpMethod(names, pageId, httpMethod); } - @Override - public Flux saveAll(List actions) { - return repository.saveAll(actions); - } - /** * This function replaces the variables in the Object with the actual params */ diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java index 9b524b0d11..e0ac89801b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java @@ -2,12 +2,15 @@ package com.appsmith.server.services; import com.appsmith.server.constants.AnalyticsEvents; import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.Action; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationPage; import com.appsmith.server.domains.Layout; +import com.appsmith.server.domains.Page; import com.appsmith.server.domains.User; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.repositories.ActionRepository; import com.appsmith.server.repositories.ApplicationRepository; import com.appsmith.server.repositories.PageRepository; import lombok.extern.slf4j.Slf4j; @@ -35,6 +38,7 @@ public class ApplicationServiceImpl extends BaseService true); } + /** + * This function performs a soft delete for the application along with it's associated pages and actions. + * + * @param id The application id to delete + * @return The modified application object with the deleted flag set + */ @Override public Mono delete(String id) { log.debug("Archiving application with id: {}", id); + Mono applicationMono = repository.findById(id) - .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "application", id))); + .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "application", id))) + .flatMap(application -> pageRepository.findByApplicationId(id) + .flatMap(page -> { + log.debug("Going to archive pageId: {} for applicationId: {}", page.getId(), id); + Mono archivedPageMono = pageRepository.archiveById(page); + Mono> archivedActionsMono = actionRepository.findByPageId(page.getId()) + .flatMap(action -> { + log.debug("Going to archive actionId: {} for applicationId: {}", action.getId(), id); + return actionRepository.archiveById(action); + }) + .collectList(); + return Mono.zip(archivedPageMono, archivedActionsMono) + .map(tuple -> { + Page page1 = tuple.getT1(); + List actions = tuple.getT2(); + log.debug("Archived pageId: {} and {} actions for applicationId: {}", page1.getId(), actions.size(), id); + return page1; + }); + }) + .collectList() + .flatMap(pages -> repository.archiveById(application))); return applicationMono - .flatMap(deletedObj -> { - deletedObj.setDeleted(true); - return repository.save(deletedObj); - }) .map(deletedObj -> { analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Application) deletedObj); return (Application) deletedObj;