diff --git a/application-engine/pom.xml b/application-engine/pom.xml index b5c876724d6..d108e3fd97a 100644 --- a/application-engine/pom.xml +++ b/application-engine/pom.xml @@ -438,11 +438,6 @@ 2.0 - - org.apache.commons - commons-lang3 - 3.17.0 - org.springframework.boot spring-boot-starter-web diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/MongoIndexesConfigurator.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/MongoIndexesConfigurator.java new file mode 100644 index 00000000000..0705a6586d1 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/MongoIndexesConfigurator.java @@ -0,0 +1,33 @@ +package com.netgrif.application.engine.configuration; + +import com.netgrif.application.engine.adapter.spring.configuration.AbstractMongoIndexesConfigurator; +import com.netgrif.application.engine.configuration.properties.DataConfigurationProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; + +import java.util.List; + +@Slf4j +@Component +public class MongoIndexesConfigurator extends AbstractMongoIndexesConfigurator { + + private final DataConfigurationProperties.MongoProperties mongoProperties; + + public MongoIndexesConfigurator(MongoTemplate mongoTemplate, + DataConfigurationProperties.MongoProperties mongoProperties) { + super(mongoTemplate); + this.mongoProperties = mongoProperties; + } + + @Override + public MultiValueMap, String> getIndexes() { + return mongoProperties.getIndexes(); + } + + @Override + public List> getEntityIndexBlacklist() { + return List.of(); + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java index 80bf1af1f3d..36cfbb61506 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java @@ -11,13 +11,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.Resource; -import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Configuration properties for the application engine's data functionality. @@ -137,6 +135,15 @@ public static class MongoProperties extends org.springframework.boot.autoconfigu * Ensures indexes are applied during initialization, if true. */ private Boolean runnerEnsureIndex = true; + + /** + * Multi-value map for MongoDB indexes. + *

+ * This property holds a mapping between entity classes and their associated + * collection of index definitions. Each entry in the map corresponds to a class + * and its set of index configurations for MongoDB. + */ + private MultiValueMap, String> indexes = new LinkedMultiValueMap<>(); } /** diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/MongoDbRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/MongoDbRunner.java index fb0a2cf8f42..f77d7af06c9 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/MongoDbRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/MongoDbRunner.java @@ -1,25 +1,16 @@ package com.netgrif.application.engine.startup.runner; +import com.netgrif.application.engine.configuration.MongoIndexesConfigurator; import com.netgrif.application.engine.configuration.properties.DataConfigurationProperties; import com.netgrif.application.engine.startup.ApplicationEngineStartupRunner; import com.netgrif.application.engine.startup.annotation.RunnerOrder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.context.annotation.Profile; -import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.index.IndexOperations; -import org.springframework.data.mongodb.core.index.IndexResolver; -import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver; -import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; -import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.stereotype.Component; -import static org.eclipse.jdt.internal.compiler.parser.Parser.name; - @Slf4j @Component @Profile("!test") @@ -31,30 +22,21 @@ public class MongoDbRunner implements ApplicationEngineStartupRunner { private final MongoTemplate mongoTemplate; + private final MongoIndexesConfigurator mongoIndexesConfigurator; + @Override public void run(ApplicationArguments args) throws Exception { if (mongoProperties.getDrop()) { if (mongoProperties.getHost() != null && mongoProperties.getPort() != null) - log.info("Dropping Mongo database {}:{}/{}", mongoProperties.getHost(), mongoProperties.getPort(), name); + log.info("Dropping Mongo database {}:{}/{}", mongoProperties.getHost(), mongoProperties.getPort(), mongoProperties.getDatabase()); else if (mongoProperties.getUri() != null) log.info("Dropping Mongo database {}", mongoProperties.getUri()); mongoTemplate.getDb().drop(); } if (mongoProperties.getRunnerEnsureIndex()) { - resolveIndexes(); + mongoIndexesConfigurator.resolveIndexes(); } } - void resolveIndexes() { - MappingContext, MongoPersistentProperty> mappingContext = mongoTemplate.getConverter().getMappingContext(); - IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext); - mappingContext.getPersistentEntities() - .stream() - .filter(it -> it.isAnnotationPresent(Document.class)) - .forEach(it -> { - IndexOperations indexOps = mongoTemplate.indexOps(it.getType()); - log.info("Ensuring existence of indexes for {}", it.getType().getSimpleName()); - resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex); - }); - } + } diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Indexed.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Indexed.java new file mode 100644 index 00000000000..32c57ef33e2 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Indexed.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.objects.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Indexed { +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java index 8cdc0198f89..6c49d9cef4d 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java @@ -1,6 +1,8 @@ package com.netgrif.application.engine.objects.auth.domain; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; +import com.netgrif.application.engine.objects.annotations.Indexed; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.objects.utils.DateUtils; import com.querydsl.core.annotations.QueryEntity; import lombok.AllArgsConstructor; diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Case.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Case.java index c408e325040..34c21683987 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Case.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Case.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.netgrif.application.engine.objects.auth.domain.ActorRef; +import com.netgrif.application.engine.objects.annotations.Indexed; import com.netgrif.application.engine.objects.petrinet.domain.I18nString; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import com.netgrif.application.engine.objects.petrinet.domain.dataset.*; @@ -38,6 +39,7 @@ public abstract class Case implements Serializable { @NotNull @Setter + @Indexed private ObjectId petriNetObjectId; @JsonIgnore @@ -54,6 +56,7 @@ public abstract class Case implements Serializable { @NotNull @Setter + @Indexed private String title; private String color; @@ -76,6 +79,7 @@ public abstract class Case implements Serializable { private List> immediateData; @Setter + @Indexed private ActorRef author; @Setter diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java index 0a1f0cc8351..eb52c24a819 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.objects.workflow.domain; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.netgrif.application.engine.objects.annotations.Indexed; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.petrinet.domain.I18nString; import com.netgrif.application.engine.objects.petrinet.domain.dataset.Field; @@ -32,14 +33,17 @@ public abstract class Task implements Serializable { private ProcessResourceId _id; @Getter + @Indexed private String processId; @Getter @Setter + @Indexed private String caseId; @Getter @Setter + @Indexed private String transitionId; @Getter @@ -63,6 +67,7 @@ public abstract class Task implements Serializable { private Integer priority; @Setter + @Indexed private String userId; @Setter diff --git a/nae-spring-core-adapter/pom.xml b/nae-spring-core-adapter/pom.xml index 18d69056e19..4186579e94e 100644 --- a/nae-spring-core-adapter/pom.xml +++ b/nae-spring-core-adapter/pom.xml @@ -103,6 +103,10 @@ querydsl-core ${querydsl.version} + + org.apache.commons + commons-lang3 + diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/AbstractMongoIndexesConfigurator.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/AbstractMongoIndexesConfigurator.java new file mode 100644 index 00000000000..bb27b1d8833 --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/AbstractMongoIndexesConfigurator.java @@ -0,0 +1,76 @@ +package com.netgrif.application.engine.adapter.spring.configuration; + +import com.netgrif.application.engine.objects.annotations.Indexed; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.*; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.util.MultiValueMap; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.StreamSupport; + +@Slf4j +public abstract class AbstractMongoIndexesConfigurator { + + private final MongoTemplate mongoTemplate; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final IndexResolver resolver; + + public AbstractMongoIndexesConfigurator(MongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + this.mappingContext = mongoTemplate.getConverter().getMappingContext(); + this.resolver = new MongoPersistentEntityIndexResolver(mappingContext); + } + + public abstract MultiValueMap, String> getIndexes(); + public abstract List> getEntityIndexBlacklist(); + + public void resolveIndexes() { + mappingContext.getPersistentEntities() + .stream() + .filter(it -> it.isAnnotationPresent(Document.class) && !getEntityIndexBlacklist().contains(it.getType())) + .forEach(mongoPersistentEntity -> resolveIndexes(mongoPersistentEntity.getCollection(), mongoPersistentEntity.getType())); + } + + public void resolveIndexes(String collectionName, Class collectionType) { + IndexOperations indexOps = mongoTemplate.indexOps(collectionName); + log.info("Ensuring existence of indexes for {}", collectionType.getSimpleName()); + List indexDefinitions = new ArrayList<>(); + Document document = collectionType.getAnnotation(Document.class); + if (document != null) { + indexDefinitions.addAll(StreamSupport.stream(resolver.resolveIndexFor(collectionType).spliterator(), false).toList()); + } + addAnnotatedFields(collectionType, indexDefinitions); + addConfiguredFields(collectionType, indexDefinitions); + indexDefinitions.forEach(indexOps::ensureIndex); + } + + private void addAnnotatedFields(Class collectionType, List indexDefinitions) { + Iterable additionalIndexedProperties = FieldUtils.getFieldsListWithAnnotation(collectionType, Indexed.class);; + additionalIndexedProperties.forEach(property -> { + String fieldName = property.getName(); + org.springframework.data.mongodb.core.mapping.Field markedField = property.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class); + if (markedField != null) { + fieldName = markedField.value(); + } + indexDefinitions.add(new Index().on(fieldName, Sort.Direction.ASC).named(fieldName)); + }); + } + + + private void addConfiguredFields(Class collectionType, List indexDefinitions) { + if (getIndexes().containsKey(collectionType)) { + getIndexes().get(collectionType).forEach(indexableFieldName -> { + indexDefinitions.add(new Index().on(indexableFieldName, Sort.Direction.ASC).named(indexableFieldName)); + }); + } + } +} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java index bb44e3bcf14..7e91f07c13f 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java @@ -8,10 +8,12 @@ import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; import com.querydsl.core.annotations.QueryEntity; import lombok.Getter; +import org.springframework.data.mongodb.core.mapping.Document; import java.util.*; @Getter +@Document @QueryEntity public class PetriNet extends com.netgrif.application.engine.objects.petrinet.domain.PetriNet { diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Case.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Case.java index 3bcca1a0fea..7b3616b57b4 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Case.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Case.java @@ -11,10 +11,12 @@ import org.springframework.data.annotation.Id; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.Transient; +import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; import java.util.*; +@Document @QueryEntity public class Case extends com.netgrif.application.engine.objects.workflow.domain.Case { diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Task.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Task.java index 10328172ed4..ce9702a0f94 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Task.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Task.java @@ -15,10 +15,12 @@ import lombok.Builder; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; +import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; import java.util.*; +@Document @QueryEntity public class Task extends com.netgrif.application.engine.objects.workflow.domain.Task { diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java index 0abe54b962d..cf77206786b 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java @@ -1,15 +1,19 @@ package com.netgrif.application.engine.auth.service; +import com.netgrif.application.engine.adapter.spring.configuration.AbstractMongoIndexesConfigurator; import com.netgrif.application.engine.auth.provider.AbstractAuthConfig; import com.netgrif.application.engine.auth.provider.AuthMethodProvider; +import com.netgrif.application.engine.auth.provider.CollectionNameProvider; import com.netgrif.application.engine.auth.provider.ProviderRegistry; import com.netgrif.application.engine.auth.realm.request.RealmSearch; import com.netgrif.application.engine.auth.repository.RealmRepository; import com.netgrif.application.engine.objects.auth.domain.Realm; +import com.netgrif.application.engine.objects.auth.domain.User; import com.netgrif.application.engine.objects.auth.provider.AuthMethod; import com.netgrif.application.engine.objects.auth.provider.AuthMethodConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -33,6 +37,20 @@ public class RealmServiceImpl implements RealmService { @Autowired private AnonymousUserRefService anonymousUserRefService; + private AbstractMongoIndexesConfigurator mongoIndexesConfigurator; + + private CollectionNameProvider collectionNameProvider; + + @Autowired + public void setMongoIndexesConfigurator(AbstractMongoIndexesConfigurator mongoIndexesConfigurator) { + this.mongoIndexesConfigurator = mongoIndexesConfigurator; + } + + @Lazy + @Autowired + public void setCollectionNameProvider(CollectionNameProvider collectionNameProvider) { + this.collectionNameProvider = collectionNameProvider; + } @Override public Realm createRealm(Realm createRequest) { @@ -45,7 +63,21 @@ public Realm createRealm(Realm createRequest) { realm.setDefaultRealm(true); } - return realmRepository.save(realm); + realm = realmRepository.save(realm); + String collectionName = collectionNameProvider.getCollectionNameForRealm(realm.getId()); + + if (!mongoTemplate.collectionExists(collectionName)) { + try { + mongoTemplate.createCollection(collectionName); + mongoIndexesConfigurator.resolveIndexes(collectionName, User.class); + } catch (Exception e) { + log.error("Error occurred while creating collection for realm {}", realm.getId(), e); + realmRepository.delete(realm); + throw new RuntimeException("Error occurred while creating collection for realm " + realm.getId(), e); + } + } + + return realm; } @Override