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.
This commit is contained in:
parent
05cfa3f72f
commit
fbada3051d
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -15,10 +15,7 @@ public interface ActionRepository extends BaseRepository<Action, String> {
|
|||
|
||||
Mono<Action> findByNameAndPageId(String name, String pageId);
|
||||
|
||||
Flux<Action> findDistinctActionsByNameInAndPageId(Set<String> names, String pageId);
|
||||
Flux<Action> findByPageId(String pageId);
|
||||
|
||||
Flux<Action> findDistinctActionsByNameInAndPageIdAndActionConfiguration_HttpMethod(Set<String> names, String pageId, String httpMethod);
|
||||
|
||||
Flux<Action> saveAll(List<Action> actions);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T, ID extends Serializable> extends ReactiveMongoRepository<T, ID> {
|
||||
|
||||
/**
|
||||
* 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<T>
|
||||
*/
|
||||
Mono<T> 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<Boolean> 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<Boolean> archiveAllById(List<ID> ids);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
* <p>
|
||||
* 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 <T> The domain class that extends {@link BaseDomain}. This is required because we use default fields in
|
||||
* {@link BaseDomain} such as `deleted`
|
||||
* @param <ID> The ID field that extends Serializable interface
|
||||
*/
|
||||
@Slf4j
|
||||
public class BaseRepositoryImpl<T extends BaseDomain, ID extends Serializable> extends SimpleReactiveMongoRepository<T, ID>
|
||||
implements BaseRepository<T, ID> {
|
||||
|
||||
private final MongoEntityInformation<T, ID> entityInformation;
|
||||
private final ReactiveMongoOperations mongoOperations;
|
||||
|
||||
public BaseRepositoryImpl(@NonNull MongoEntityInformation<T, ID> 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<T> 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<T> findAll() {
|
||||
Query query = new Query(notDeleted());
|
||||
return mongoOperations.find(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> 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<Boolean> 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<Boolean> archiveAllById(List<ID> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Action, String> {
|
||||
|
|
@ -17,11 +16,7 @@ public interface ActionService extends CrudService<Action, String> {
|
|||
|
||||
Mono<Action> findByNameAndPageId(String name, String pageId);
|
||||
|
||||
Flux<Action> findDistinctActionsByNameInAndPageId(Set<String> names, String pageId);
|
||||
|
||||
Flux<Action> findDistinctRestApiActionsByNameInAndPageIdAndHttpMethod(Set<String> names, String pageId, String httpMethod);
|
||||
|
||||
Flux<Action> saveAll(List<Action> actions);
|
||||
|
||||
public Action extractAndSetJsonPathKeys(Action action);
|
||||
Action extractAndSetJsonPathKeys(Action action);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -451,21 +451,11 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
return repository.findByNameAndPageId(name, pageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Action> findDistinctActionsByNameInAndPageId(Set<String> names, String pageId) {
|
||||
return repository.findDistinctActionsByNameInAndPageId(names, pageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Action> findDistinctRestApiActionsByNameInAndPageIdAndHttpMethod(Set<String> names, String pageId, String httpMethod) {
|
||||
return repository.findDistinctActionsByNameInAndPageIdAndActionConfiguration_HttpMethod(names, pageId, httpMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Action> saveAll(List<Action> actions) {
|
||||
return repository.saveAll(actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function replaces the variables in the Object with the actual params
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<ApplicationRepository, A
|
|||
//Using PageRepository instead of PageService is because a cyclic dependency is introduced if PageService is used here.
|
||||
//TODO : Solve for this across LayoutService, PageService and ApplicationService.
|
||||
private final PageRepository pageRepository;
|
||||
private final ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
public ApplicationServiceImpl(Scheduler scheduler,
|
||||
|
|
@ -44,10 +48,12 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
|
|||
ApplicationRepository repository,
|
||||
AnalyticsService analyticsService,
|
||||
SessionUserService sessionUserService,
|
||||
PageRepository pageRepository) {
|
||||
PageRepository pageRepository,
|
||||
ActionRepository actionRepository) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.sessionUserService = sessionUserService;
|
||||
this.pageRepository = pageRepository;
|
||||
this.actionRepository = actionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -139,17 +145,40 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
|
|||
.map(pages -> 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<Application> delete(String id) {
|
||||
log.debug("Archiving application with id: {}", id);
|
||||
|
||||
Mono<Application> 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<Page> archivedPageMono = pageRepository.archiveById(page);
|
||||
Mono<List<Action>> 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<Action> 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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user