This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new 18150fe307 [SYNCOPE-1787] RealmDAO for Elasticsearch and OpenSearch
(#544)
18150fe307 is described below
commit 18150fe307e505e5f0fad2993c1e4fce5a8c982e
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Mon Nov 6 15:25:48 2023 +0100
[SYNCOPE-1787] RealmDAO for Elasticsearch and OpenSearch (#544)
---
.../syncope/core/persistence/api/dao/RealmDAO.java | 6 +
.../core/persistence/jpa/PersistenceContext.java | 4 +-
.../core/persistence/jpa/dao/JPAAnyObjectDAO.java | 8 +-
.../core/persistence/jpa/dao/JPAAnySearchDAO.java | 5 +-
.../core/persistence/jpa/dao/JPADynRealmDAO.java | 6 +-
.../core/persistence/jpa/dao/JPAGroupDAO.java | 32 +--
.../core/persistence/jpa/dao/JPARealmDAO.java | 61 +++++-
.../core/persistence/jpa/dao/JPARoleDAO.java | 6 +-
.../core/persistence/jpa/dao/JPAUserDAO.java | 8 +-
...fecycleEvent.java => EntityLifecycleEvent.java} | 14 +-
.../AbstractPropagationTaskExecutor.java | 4 +-
.../java/AbstractAnyObjectWorkflowAdapter.java | 8 +-
.../workflow/java/AbstractUserWorkflowAdapter.java | 8 +-
.../java/DefaultAnyObjectWorkflowAdapter.java | 10 +-
.../workflow/java/DefaultGroupWorkflowAdapter.java | 10 +-
.../workflow/java/DefaultUserWorkflowAdapter.java | 18 +-
.../client/ElasticsearchIndexLoader.java | 5 +
.../client/ElasticsearchIndexManager.java | 130 +++++++++---
.../elasticsearch/client/ElasticsearchUtils.java | 24 ++-
.../jpa/ElasticsearchPersistenceContext.java | 14 ++
.../jpa/dao/ElasticsearchAnySearchDAO.java | 6 +-
.../persistence/jpa/dao/ElasticsearchRealmDAO.java | 230 +++++++++++++++++++++
.../jpa/dao/ElasticsearchAnySearchDAOTest.java | 12 +-
.../java/job/ElasticsearchReindex.java | 37 ++++
.../core/flowable/api/UserRequestHandler.java | 6 +-
.../flowable/impl/FlowableUserRequestHandler.java | 10 +-
.../flowable/impl/FlowableUserWorkflowAdapter.java | 24 +--
.../opensearch/client/OpenSearchIndexLoader.java | 5 +
.../opensearch/client/OpenSearchIndexManager.java | 136 +++++++++---
.../ext/opensearch/client/OpenSearchUtils.java | 24 ++-
.../jpa/OpenSearchPersistenceContext.java | 14 ++
.../jpa/dao/OpenSearchAnySearchDAO.java | 6 +-
.../persistence/jpa/dao/OpenSearchRealmDAO.java | 230 +++++++++++++++++++++
.../jpa/dao/OpenSearchAnySearchDAOTest.java | 12 +-
.../provisioning/java/job/OpenSearchReindex.java | 37 ++++
.../fit/core/reference/ITImplementationLookup.java | 9 -
.../reference-guide/concepts/extensions.adoc | 8 +-
37 files changed, 1006 insertions(+), 181 deletions(-)
diff --git
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
index e05eb840b5..a0a573caaa 100644
---
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
+++
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
@@ -45,6 +45,8 @@ public interface RealmDAO extends DAO<Realm> {
List<Realm> findDescendants(String base, String keyword, int page, int
itemsPerPage);
+ List<String> findDescendants(String base, String prefix);
+
<T extends Policy> List<Realm> findByPolicy(T policy);
List<Realm> findByLogicActions(Implementation logicActions);
@@ -53,6 +55,10 @@ public interface RealmDAO extends DAO<Realm> {
List<Realm> findChildren(Realm realm);
+ int count();
+
+ List<String> findAllKeys(int page, int itemsPerPage);
+
Realm save(Realm realm);
void delete(Realm realm);
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
index b6d4c43a85..aee5fbfe43 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
@@ -605,8 +605,8 @@ public class PersistenceContext {
@ConditionalOnMissingBean
@Bean
- public RealmDAO realmDAO(final @Lazy RoleDAO roleDAO) {
- return new JPARealmDAO(roleDAO);
+ public RealmDAO realmDAO(final @Lazy RoleDAO roleDAO, final
ApplicationEventPublisher publisher) {
+ return new JPARealmDAO(roleDAO, publisher);
}
@ConditionalOnMissingBean
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
index 4e219060ab..50d98e608f 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
@@ -147,7 +147,7 @@ public class JPAAnyObjectDAO extends
AbstractAnyDAO<AnyObject> implements AnyObj
@Override
public Map<AnyType, Integer> countByType() {
Query query = entityManager().createQuery(
- "SELECT e.type, COUNT(e) AS countByType FROM " +
anyUtils().anyClass().getSimpleName() + " e "
+ "SELECT e.type, COUNT(e) AS countByType FROM " +
anyUtils().anyClass().getSimpleName() + " e "
+ "GROUP BY e.type ORDER BY countByType DESC");
@SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList();
@@ -161,7 +161,7 @@ public class JPAAnyObjectDAO extends
AbstractAnyDAO<AnyObject> implements AnyObj
@Override
public Map<String, Integer> countByRealm(final AnyType anyType) {
Query query = entityManager().createQuery(
- "SELECT e.realm, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e "
+ "SELECT e.realm, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e "
+ "WHERE e.type=:type GROUP BY e.realm");
query.setParameter("type", anyType);
@@ -240,14 +240,14 @@ public class JPAAnyObjectDAO extends
AbstractAnyDAO<AnyObject> implements AnyObj
@Override
public int count() {
Query query = entityManager().createQuery(
- "SELECT COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e");
+ "SELECT COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e");
return ((Number) query.getSingleResult()).intValue();
}
@Override
public List<AnyObject> findAll(final int page, final int itemsPerPage) {
TypedQuery<AnyObject> query = entityManager().createQuery(
- "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + "
e ORDER BY e.id", AnyObject.class);
+ "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e
ORDER BY e.id", AnyObject.class);
query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
query.setMaxResults(itemsPerPage);
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index 4e6d29aeb2..b069639bda 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -138,10 +138,7 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
return noRealm;
});
- realmKeys.addAll(
-
realmDAO.findDescendants(realm.getFullPath(), null, -1, -1).stream().
- filter(r ->
r.getFullPath().startsWith(base.getFullPath())).
-
map(Realm::getKey).collect(Collectors.toSet()));
+
realmKeys.addAll(realmDAO.findDescendants(realm.getFullPath(),
base.getFullPath()));
} else {
DynRealm dynRealm = dynRealmDAO.find(realmPath);
if (dynRealm == null) {
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
index 716c3dc829..de05bcb6e5 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
@@ -33,7 +33,7 @@ import
org.apache.syncope.core.persistence.api.entity.DynRealm;
import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.persistence.jpa.entity.JPADynRealm;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.springframework.context.ApplicationEventPublisher;
@@ -122,7 +122,7 @@ public class JPADynRealmDAO extends AbstractDAO<DynRealm>
implements DynRealmDAO
}
if (any != null) {
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
any, AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
any, AuthContextUtils.getDomain()));
}
});
}
@@ -144,7 +144,7 @@ public class JPADynRealmDAO extends AbstractDAO<DynRealm>
implements DynRealmDAO
insert.executeUpdate();
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, any,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
any, AuthContextUtils.getDomain()));
cleared.remove(any.getKey());
}));
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
index bdb3d9da96..27f7b479ba 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
@@ -65,7 +65,7 @@ import
org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
import org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension;
import
org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import
org.apache.syncope.core.spring.security.DelegatedAdministrationException;
@@ -147,14 +147,14 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
@Override
public int count() {
Query query = entityManager().createQuery(
- "SELECT COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e");
+ "SELECT COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e");
return ((Number) query.getSingleResult()).intValue();
}
@Override
public Map<String, Integer> countByRealm() {
Query query = entityManager().createQuery(
- "SELECT e.realm, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e GROUP BY e.realm");
+ "SELECT e.realm, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e GROUP BY e.realm");
@SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList();
@@ -277,7 +277,7 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
@Override
public List<Group> findAll(final int page, final int itemsPerPage) {
TypedQuery<Group> query = entityManager().createQuery(
- "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + "
e ORDER BY e.id", Group.class);
+ "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e
ORDER BY e.id", Group.class);
query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
query.setMaxResults(itemsPerPage);
@@ -321,7 +321,7 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
insert.executeUpdate();
publisher.publishEvent(
- new AnyLifecycleEvent<>(this,
SyncDeltaType.UPDATE, user, AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this,
SyncDeltaType.UPDATE, user, AuthContextUtils.getDomain()));
});
}
}
@@ -350,7 +350,7 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
insert.executeUpdate();
publisher.publishEvent(
- new AnyLifecycleEvent<>(this,
SyncDeltaType.UPDATE, any, AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this,
SyncDeltaType.UPDATE, any, AuthContextUtils.getDomain()));
});
}
});
@@ -377,7 +377,7 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
anyObjectDAO.save(leftEnd);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
leftEnd, AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
leftEnd, AuthContextUtils.getDomain()));
});
findUMemberships(group).forEach(membership -> {
@@ -394,7 +394,7 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
userDAO.save(leftEnd);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
leftEnd, AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
leftEnd, AuthContextUtils.getDomain()));
});
clearUDynMembers(group);
@@ -582,8 +582,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
delete.executeUpdate();
}
- publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
memb.getGroup(), AuthContextUtils.getDomain()));
+ publisher.publishEvent(new EntityLifecycleEvent<>(
+ this, SyncDeltaType.UPDATE, memb.getGroup(),
AuthContextUtils.getDomain()));
});
return Pair.of(before, after);
@@ -601,8 +601,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
dynGroups.forEach(group -> {
before.add(group.getKey());
- publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain()));
+ publisher.publishEvent(new EntityLifecycleEvent<>(
+ this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain()));
});
return before;
@@ -680,8 +680,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
delete.executeUpdate();
}
- publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
memb.getGroup(), AuthContextUtils.getDomain()));
+ publisher.publishEvent(new EntityLifecycleEvent<>(
+ this, SyncDeltaType.UPDATE, memb.getGroup(),
AuthContextUtils.getDomain()));
});
return Pair.of(before, after);
@@ -699,8 +699,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group>
implements GroupDAO {
dynGroups.forEach(group -> {
before.add(group.getKey());
- publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain()));
+ publisher.publishEvent(new EntityLifecycleEvent<>(
+ this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain()));
});
return before;
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
index 7f37732fc9..8bc604440d 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
@@ -23,6 +23,7 @@ import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
@@ -41,14 +42,21 @@ import
org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import
org.apache.syncope.core.persistence.api.entity.policy.ProvisioningPolicy;
import
org.apache.syncope.core.persistence.api.entity.policy.TicketExpirationPolicy;
import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.transaction.annotation.Transactional;
public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
protected final RoleDAO roleDAO;
- public JPARealmDAO(final RoleDAO roleDAO) {
+ protected final ApplicationEventPublisher publisher;
+
+ public JPARealmDAO(final RoleDAO roleDAO, final ApplicationEventPublisher
publisher) {
this.roleDAO = roleDAO;
+ this.publisher = publisher;
}
@Override
@@ -183,6 +191,27 @@ public class JPARealmDAO extends AbstractDAO<Realm>
implements RealmDAO {
return query.getResultList();
}
+ @Override
+ public List<String> findDescendants(final String base, final String
prefix) {
+ List<Object> parameters = new ArrayList<>();
+
+ StringBuilder queryString = buildDescendantQuery(base, null,
parameters);
+ TypedQuery<Realm> query = entityManager().createQuery(queryString.
+ append(" AND (e.fullPath=?").
+ append(setParameter(parameters, prefix)).
+ append(" OR e.fullPath LIKE ?").
+ append(setParameter(parameters,
SyncopeConstants.ROOT_REALM.equals(prefix) ? "/%" : prefix + "/%")).
+ append(')').
+ append(" ORDER BY e.fullPath").toString(),
+ Realm.class);
+
+ for (int i = 1; i <= parameters.size(); i++) {
+ query.setParameter(i, parameters.get(i - 1));
+ }
+
+ return
query.getResultList().stream().map(Realm::getKey).collect(Collectors.toList());
+ }
+
protected <T extends Policy> List<Realm> findSamePolicyChildren(final
Realm realm, final T policy) {
List<Realm> result = new ArrayList<>();
@@ -284,6 +313,30 @@ public class JPARealmDAO extends AbstractDAO<Realm>
implements RealmDAO {
: StringUtils.appendIfMissing(realm.getParent().getFullPath(),
"/") + realm.getName();
}
+ @Override
+ public int count() {
+ Query query = entityManager().createNativeQuery(
+ "SELECT COUNT(id) FROM " + JPARealm.TABLE);
+ return ((Number) query.getSingleResult()).intValue();
+ }
+
+ @Override
+ public List<String> findAllKeys(final int page, final int itemsPerPage) {
+ Query query = entityManager().createNativeQuery("SELECT id FROM " +
JPARealm.TABLE + " ORDER BY fullPath");
+
+ query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
+
+ if (itemsPerPage > 0) {
+ query.setMaxResults(itemsPerPage);
+ }
+
+ @SuppressWarnings("unchecked")
+ List<Object> raw = query.getResultList();
+ return raw.stream().map(key -> key instanceof Object[]
+ ? (String) ((Object[]) key)[0]
+ : ((String) key)).collect(Collectors.toList());
+ }
+
@Override
public Realm save(final Realm realm) {
String fullPathBefore = realm.getFullPath();
@@ -298,6 +351,9 @@ public class JPARealmDAO extends AbstractDAO<Realm>
implements RealmDAO {
findChildren(realm).forEach(this::save);
}
+ publisher.publishEvent(
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, merged,
AuthContextUtils.getDomain()));
+
return merged;
}
@@ -313,6 +369,9 @@ public class JPARealmDAO extends AbstractDAO<Realm>
implements RealmDAO {
toBeDeleted.setParent(null);
entityManager().remove(toBeDeleted);
+
+ publisher.publishEvent(
+ new EntityLifecycleEvent<>(this, SyncDeltaType.DELETE,
toBeDeleted, AuthContextUtils.getDomain()));
});
}
}
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
index 2064b8f6bb..ee91ccb6f5 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
@@ -35,7 +35,7 @@ import
org.apache.syncope.core.persistence.api.search.SearchCondConverter;
import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.persistence.jpa.entity.JPARole;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.springframework.context.ApplicationEventPublisher;
@@ -129,7 +129,7 @@ public class JPARoleDAO extends AbstractDAO<Role>
implements RoleDAO {
insert.executeUpdate();
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
user, AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
user, AuthContextUtils.getDomain()));
});
}
@@ -145,7 +145,7 @@ public class JPARoleDAO extends AbstractDAO<Role>
implements RoleDAO {
query.getResultList().forEach(user -> {
user.getRoles().remove(role);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, user,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
user, AuthContextUtils.getDomain()));
});
clearDynMembers(role);
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index ed40bb3cf7..61dfa5e631 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -147,14 +147,14 @@ public class JPAUserDAO extends AbstractAnyDAO<User>
implements UserDAO {
@Override
public int count() {
Query query = entityManager().createQuery(
- "SELECT COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e");
+ "SELECT COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e");
return ((Number) query.getSingleResult()).intValue();
}
@Override
public Map<String, Integer> countByRealm() {
Query query = entityManager().createQuery(
- "SELECT e.realm, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e GROUP BY e.realm");
+ "SELECT e.realm, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e GROUP BY e.realm");
@SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList();
@@ -166,7 +166,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User>
implements UserDAO {
@Override
public Map<String, Integer> countByStatus() {
Query query = entityManager().createQuery(
- "SELECT e.status, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e GROUP BY e.status");
+ "SELECT e.status, COUNT(e) FROM " +
anyUtils().anyClass().getSimpleName() + " e GROUP BY e.status");
@SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList();
@@ -270,7 +270,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User>
implements UserDAO {
@Override
public List<User> findAll(final int page, final int itemsPerPage) {
TypedQuery<User> query = entityManager().createQuery(
- "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + "
e ORDER BY e.id", User.class);
+ "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e
ORDER BY e.id", User.class);
query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
query.setMaxResults(itemsPerPage);
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyLifecycleEvent.java
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/EntityLifecycleEvent.java
similarity index 78%
rename from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyLifecycleEvent.java
rename to
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/EntityLifecycleEvent.java
index 8fbc75f1c3..faab2acc76 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyLifecycleEvent.java
+++
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/EntityLifecycleEvent.java
@@ -18,25 +18,25 @@
*/
package org.apache.syncope.core.provisioning.api.event;
-import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.Entity;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.springframework.context.ApplicationEvent;
-public class AnyLifecycleEvent<A extends Any<?>> extends ApplicationEvent {
+public class EntityLifecycleEvent<E extends Entity> extends ApplicationEvent {
private static final long serialVersionUID = -781747175059834365L;
private final SyncDeltaType type;
- private final A any;
+ private final E entity;
private final String domain;
- public AnyLifecycleEvent(final Object source, final SyncDeltaType type,
final A any, final String domain) {
+ public EntityLifecycleEvent(final Object source, final SyncDeltaType type,
final E entity, final String domain) {
super(source);
this.type = type;
- this.any = any;
+ this.entity = entity;
this.domain = domain;
}
@@ -44,8 +44,8 @@ public class AnyLifecycleEvent<A extends Any<?>> extends
ApplicationEvent {
return type;
}
- public A getAny() {
- return any;
+ public E getEntity() {
+ return entity;
}
public String getDomain() {
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index a080189dd8..a20aa19cc3 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -58,7 +58,7 @@ import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.ConnectorManager;
import org.apache.syncope.core.provisioning.api.TimeoutException;
import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
@@ -236,7 +236,7 @@ public abstract class AbstractPropagationTaskExecutor
implements PropagationTask
taskInfo.getEntityKey(),
plainSchemaDAO.find(provision.getUidOnCreate()),
result.getUidValue());
- publisher.publishEvent(new AnyLifecycleEvent<>(
+ publisher.publishEvent(new EntityLifecycleEvent<>(
this,
SyncDeltaType.UPDATE,
anyUtils.dao().find(taskInfo.getEntityKey()),
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
index a5cf36a090..35158dca12 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
@@ -35,7 +35,7 @@ import
org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.provisioning.api.WorkflowResult;
import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
@@ -78,7 +78,7 @@ public abstract class AbstractAnyObjectWorkflowAdapter
// finally publish events for all groups affected by this operation,
via membership
anyObject.getMemberships().stream().forEach(m ->
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
m.getRightEnd(), AuthContextUtils.getDomain())));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
m.getRightEnd(), AuthContextUtils.getDomain())));
return result;
}
@@ -109,7 +109,7 @@ public abstract class AbstractAnyObjectWorkflowAdapter
// finally publish events for all groups affected by this operation,
via membership
result.getResult().getMemberships().stream().map(MembershipUR::getGroup).distinct().
map(groupDAO::find).filter(Objects::nonNull).
- forEach(group -> publisher.publishEvent(new
AnyLifecycleEvent<>(
+ forEach(group -> publisher.publishEvent(new
EntityLifecycleEvent<>(
this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain())));
return result;
@@ -127,7 +127,7 @@ public abstract class AbstractAnyObjectWorkflowAdapter
doDelete(anyObject, eraser, context);
// finally publish events for all groups affected by this operation,
via membership
- groups.forEach(group -> publisher.publishEvent(new AnyLifecycleEvent<>(
+ groups.forEach(group -> publisher.publishEvent(new
EntityLifecycleEvent<>(
this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain())));
}
}
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
index 669b80e9a5..1626dfbabe 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
@@ -46,7 +46,7 @@ import
org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.policy.AccountPolicyException;
import org.apache.syncope.core.spring.policy.PasswordPolicyException;
@@ -250,7 +250,7 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
// finally publish events for all groups affected by this operation,
via membership
user.getMemberships().stream().forEach(m -> publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
m.getRightEnd(), AuthContextUtils.getDomain())));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
m.getRightEnd(), AuthContextUtils.getDomain())));
return result;
}
@@ -318,7 +318,7 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
// finally publish events for all groups affected by this operation,
via membership
result.getResult().getLeft().getMemberships().stream().map(MembershipUR::getGroup).distinct().
map(groupDAO::find).filter(Objects::nonNull).
- forEach(group -> publisher.publishEvent(new
AnyLifecycleEvent<>(
+ forEach(group -> publisher.publishEvent(new
EntityLifecycleEvent<>(
this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain())));
return result;
@@ -410,7 +410,7 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
doDelete(user, eraser, context);
// finally publish events for all groups affected by this operation,
via membership
- groups.forEach(group -> publisher.publishEvent(new AnyLifecycleEvent<>(
+ groups.forEach(group -> publisher.publishEvent(new
EntityLifecycleEvent<>(
this, SyncDeltaType.UPDATE, group,
AuthContextUtils.getDomain())));
}
}
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java
index c2bc29dd19..2f787eae1a 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java
@@ -28,7 +28,7 @@ import
org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.WorkflowResult;
import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.springframework.context.ApplicationEventPublisher;
@@ -58,7 +58,7 @@ public class DefaultAnyObjectWorkflowAdapter extends
AbstractAnyObjectWorkflowAd
anyObject = anyObjectDAO.save(anyObject);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, anyObject,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.CREATE,
anyObject, AuthContextUtils.getDomain()));
PropagationByResource<String> propByRes = new
PropagationByResource<>();
propByRes.set(ResourceOperation.CREATE,
anyObjectDAO.findAllResourceKeys(anyObject.getKey()));
@@ -74,8 +74,8 @@ public class DefaultAnyObjectWorkflowAdapter extends
AbstractAnyObjectWorkflowAd
metadata(anyObject, updater, context);
AnyObject updated = anyObjectDAO.save(anyObject);
- publisher.publishEvent(new AnyLifecycleEvent<>(
- this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ publisher.publishEvent(
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
return new WorkflowResult<>(anyObjectUR, propByRes, "update");
}
@@ -85,6 +85,6 @@ public class DefaultAnyObjectWorkflowAdapter extends
AbstractAnyObjectWorkflowAd
anyObjectDAO.delete(anyObject);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, anyObject,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.DELETE,
anyObject, AuthContextUtils.getDomain()));
}
}
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java
index 3c1175385d..2cab7cdd42 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java
@@ -27,7 +27,7 @@ import
org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.WorkflowResult;
import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.springframework.context.ApplicationEventPublisher;
@@ -54,7 +54,7 @@ public class DefaultGroupWorkflowAdapter extends
AbstractGroupWorkflowAdapter {
group = groupDAO.saveAndRefreshDynMemberships(group);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, group,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.CREATE, group,
AuthContextUtils.getDomain()));
PropagationByResource<String> propByRes = new
PropagationByResource<>();
propByRes.set(ResourceOperation.CREATE,
groupDAO.findAllResourceKeys(group.getKey()));
@@ -70,8 +70,8 @@ public class DefaultGroupWorkflowAdapter extends
AbstractGroupWorkflowAdapter {
metadata(group, updater, context);
Group updated = groupDAO.save(group);
- publisher.publishEvent(new AnyLifecycleEvent<>(
- this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ publisher.publishEvent(
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
return new WorkflowResult<>(groupUR, propByRes, "update");
}
@@ -81,6 +81,6 @@ public class DefaultGroupWorkflowAdapter extends
AbstractGroupWorkflowAdapter {
groupDAO.delete(group);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, group,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.DELETE, group,
AuthContextUtils.getDomain()));
}
}
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
index 977f162c6d..36509fbc05 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
@@ -32,7 +32,7 @@ import
org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.SecurityProperties;
@@ -91,7 +91,7 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
user = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, user,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.CREATE, user,
AuthContextUtils.getDomain()));
PropagationByResource<String> propByRes = new
PropagationByResource<>();
propByRes.set(ResourceOperation.CREATE,
userDAO.findAllResourceKeys(user.getKey()));
@@ -122,7 +122,7 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
return new UserWorkflowResult<>(updated.getKey(), null, null,
"activate");
}
@@ -137,8 +137,8 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
metadata(user, updater, context);
User updated = userDAO.save(user);
- publisher.publishEvent(new AnyLifecycleEvent<>(
- this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ publisher.publishEvent(
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
return new UserWorkflowResult<>(
Pair.of(userUR, !user.isSuspended()),
@@ -154,7 +154,7 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
return new UserWorkflowResult<>(updated.getKey(), null, null,
"suspend");
}
@@ -166,7 +166,7 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
return new UserWorkflowResult<>(updated.getKey(), null, null,
"reactivate");
}
@@ -180,7 +180,7 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
}
@Override
@@ -208,6 +208,6 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
userDAO.delete(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, user,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.DELETE, user,
AuthContextUtils.getDomain()));
}
}
diff --git
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexLoader.java
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexLoader.java
index c3fe69d34d..93e55ed7b5 100644
---
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexLoader.java
+++
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexLoader.java
@@ -56,6 +56,11 @@ public class ElasticsearchIndexLoader implements
SyncopeCoreLoader {
indexManager.defaultSettings(),
indexManager.defaultAnyMapping());
}
+ if (!indexManager.existsRealmIndex(domain)) {
+ indexManager.createRealmIndex(domain,
+ indexManager.defaultSettings(),
indexManager.defaultRealmMapping());
+ }
+
if (!indexManager.existsAuditIndex(domain)) {
indexManager.createAuditIndex(domain,
indexManager.defaultSettings(),
indexManager.defaultAuditMapping());
diff --git
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
index c426431b48..919ff7312f 100644
---
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
+++
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
@@ -20,6 +20,7 @@ package org.apache.syncope.ext.elasticsearch.client;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
+import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch._types.analysis.CustomNormalizer;
import co.elastic.clients.elasticsearch._types.analysis.Normalizer;
import co.elastic.clients.elasticsearch._types.mapping.DynamicTemplate;
@@ -45,7 +46,9 @@ import java.util.List;
import java.util.Map;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.persistence.api.entity.Any;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.slf4j.Logger;
@@ -85,6 +88,12 @@ public class ElasticsearchIndexManager {
value();
}
+ public boolean existsRealmIndex(final String domain) throws IOException {
+ return client.indices().exists(new ExistsRequest.Builder().
+ index(ElasticsearchUtils.getRealmIndex(domain)).build()).
+ value();
+ }
+
public boolean existsAuditIndex(final String domain) throws IOException {
return client.indices().exists(new ExistsRequest.Builder().
index(ElasticsearchUtils.getAuditIndex(domain)).build()).
@@ -119,6 +128,19 @@ public class ElasticsearchIndexManager {
build();
}
+ public TypeMapping defaultRealmMapping() throws IOException {
+ return new TypeMapping.Builder().
+ dynamicTemplates(List.of(Map.of(
+ "strings",
+ new DynamicTemplate.Builder().
+ matchMappingType("string").
+ mapping(new Property.Builder().
+ keyword(new
KeywordProperty.Builder().normalizer("string_lowercase").build()).
+ build()).
+ build()))).
+ build();
+ }
+
public TypeMapping defaultAuditMapping() throws IOException {
return new TypeMapping.Builder().
dynamicTemplates(List.of(Map.of(
@@ -198,6 +220,45 @@ public class ElasticsearchIndexManager {
LOG.debug("Successfully removed {}: {}",
ElasticsearchUtils.getAnyIndex(domain, kind), response);
}
+ protected CreateIndexResponse doCreateRealmIndex(
+ final String domain,
+ final IndexSettings settings,
+ final TypeMapping mappings) throws IOException {
+
+ return client.indices().create(
+ new CreateIndexRequest.Builder().
+ index(ElasticsearchUtils.getRealmIndex(domain)).
+ settings(settings).
+ mappings(mappings).
+ build());
+ }
+
+ public void createRealmIndex(
+ final String domain,
+ final IndexSettings settings,
+ final TypeMapping mappings)
+ throws IOException {
+
+ try {
+ CreateIndexResponse response = doCreateRealmIndex(domain,
settings, mappings);
+
+ LOG.debug("Successfully created realm index {}: {}",
+ ElasticsearchUtils.getRealmIndex(domain), response);
+ } catch (ElasticsearchException e) {
+ LOG.debug("Could not create realm index {} because it already
exists",
+ ElasticsearchUtils.getRealmIndex(domain), e);
+
+ removeRealmIndex(domain);
+ doCreateRealmIndex(domain, settings, mappings);
+ }
+ }
+
+ public void removeRealmIndex(final String domain) throws IOException {
+ DeleteIndexResponse response = client.indices().delete(
+ new
DeleteIndexRequest.Builder().index(ElasticsearchUtils.getRealmIndex(domain)).build());
+ LOG.debug("Successfully removed {}: {}",
ElasticsearchUtils.getRealmIndex(domain), response);
+ }
+
protected CreateIndexResponse doCreateAuditIndex(
final String domain,
final IndexSettings settings,
@@ -238,31 +299,54 @@ public class ElasticsearchIndexManager {
}
@TransactionalEventListener
- public void any(final AnyLifecycleEvent<Any<?>> event) throws IOException {
- LOG.debug("About to {} index for {}", event.getType().name(),
event.getAny());
-
- if (event.getType() == SyncDeltaType.DELETE) {
- DeleteRequest request = new DeleteRequest.Builder().index(
- ElasticsearchUtils.getAnyIndex(event.getDomain(),
event.getAny().getType().getKind())).
- id(event.getAny().getKey()).
- build();
- DeleteResponse response = client.delete(request);
- LOG.debug("Index successfully deleted for {}[{}]: {}",
- event.getAny().getType().getKind(),
event.getAny().getKey(), response);
- } else {
- IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
- index(ElasticsearchUtils.getAnyIndex(event.getDomain(),
event.getAny().getType().getKind())).
- id(event.getAny().getKey()).
- document(elasticsearchUtils.document(event.getAny())).
- build();
- IndexResponse response = client.index(request);
- LOG.debug("Index successfully created or updated for {}: {}",
event.getAny(), response);
+ public void entity(final EntityLifecycleEvent<Entity> event) throws
IOException {
+ LOG.debug("About to {} index for {}", event.getType().name(),
event.getEntity());
+
+ if (event.getEntity() instanceof Any) {
+ Any<?> any = (Any<?>) event.getEntity();
+
+ if (event.getType() == SyncDeltaType.DELETE) {
+ DeleteRequest request = new DeleteRequest.Builder().index(
+ ElasticsearchUtils.getAnyIndex(event.getDomain(),
any.getType().getKind())).
+ id(any.getKey()).
+ build();
+ DeleteResponse response = client.delete(request);
+ LOG.debug("Index successfully deleted for {}[{}]: {}",
+ any.getType().getKind(), any.getKey(), response);
+ } else {
+ IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
+
index(ElasticsearchUtils.getAnyIndex(event.getDomain(),
any.getType().getKind())).
+ id(any.getKey()).
+ document(elasticsearchUtils.document(any)).
+ build();
+ IndexResponse response = client.index(request);
+ LOG.debug("Index successfully created or updated for {}: {}",
any, response);
+ }
+ } else if (event.getEntity() instanceof Realm) {
+ Realm realm = (Realm) event.getEntity();
+
+ if (event.getType() == SyncDeltaType.DELETE) {
+ DeleteRequest request = new DeleteRequest.Builder().
+
index(ElasticsearchUtils.getRealmIndex(event.getDomain())).
+ id(realm.getKey()).
+ refresh(Refresh.True).
+ build();
+ DeleteResponse response = client.delete(request);
+ LOG.debug("Index successfully deleted for {}: {}", realm,
response);
+ } else {
+ IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
+
index(ElasticsearchUtils.getRealmIndex(event.getDomain())).
+ id(realm.getKey()).
+ document(elasticsearchUtils.document(realm)).
+ refresh(Refresh.True).
+ build();
+ IndexResponse response = client.index(request);
+ LOG.debug("Index successfully created or updated for {}: {}",
realm, response);
+ }
}
}
- public void audit(final String domain, final long instant, final JsonNode
message)
- throws IOException {
-
+ public void audit(final String domain, final long instant, final JsonNode
message) throws IOException {
LOG.debug("About to audit");
IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
diff --git
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
index 1326487fd5..f4e17e8da8 100644
---
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
+++
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
@@ -38,6 +38,7 @@ import
org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.Privilege;
+import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -52,6 +53,10 @@ public class ElasticsearchUtils {
return domain.toLowerCase() + '_' + kind.name().toLowerCase();
}
+ public static String getRealmIndex(final String domain) {
+ return domain.toLowerCase() + "_realm";
+ }
+
public static String getAuditIndex(final String domain) {
return domain.toLowerCase() + "_audit";
}
@@ -121,7 +126,7 @@ public class ElasticsearchUtils {
builder.put("relationships", relationships);
builder.put("relationshipTypes", relationshipTypes);
- ElasticsearchUtils.this.customizeDocument(builder, anyObject);
+ customizeDocument(builder, anyObject);
} else if (any instanceof Group) {
Group group = ((Group) any);
builder.put("name", group.getName());
@@ -137,7 +142,7 @@ public class ElasticsearchUtils {
members.addAll(groupDAO.findADynMembers(group));
builder.put("members", members);
- ElasticsearchUtils.this.customizeDocument(builder, group);
+ customizeDocument(builder, group);
} else if (any instanceof User) {
User user = ((User) any);
builder.put("username", user.getUsername());
@@ -193,6 +198,21 @@ public class ElasticsearchUtils {
protected void customizeDocument(final Map<String, Object> builder, final
User user) {
}
+ public Map<String, Object> document(final Realm realm) {
+ Map<String, Object> builder = new HashMap<>();
+ builder.put("id", realm.getKey());
+ builder.put("name", realm.getName());
+ builder.put("parent_id", realm.getParent() == null ? null :
realm.getParent().getKey());
+ builder.put("fullPath", realm.getFullPath());
+
+ customizeDocument(builder, realm);
+
+ return builder;
+ }
+
+ protected void customizeDocument(final Map<String, Object> builder, final
Realm realm) {
+ }
+
public Map<String, Object> document(
final long instant,
final JsonNode message,
diff --git
a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
index 88e9e1e160..fb6f7cc495 100644
---
a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
+++
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
@@ -27,13 +27,16 @@ import
org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAnySearchDAO;
import org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAuditConfDAO;
+import org.apache.syncope.core.persistence.jpa.dao.ElasticsearchRealmDAO;
import org.apache.syncope.ext.elasticsearch.client.ElasticsearchProperties;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@@ -70,6 +73,17 @@ public class ElasticsearchPersistenceContext {
props.getIndexMaxResultWindow());
}
+ @ConditionalOnMissingBean(name = "elasticsearchRealmDAO")
+ @Bean
+ public RealmDAO realmDAO(
+ final @Lazy RoleDAO roleDAO,
+ final ApplicationEventPublisher publisher,
+ final ElasticsearchProperties props,
+ final ElasticsearchClient client) {
+
+ return new ElasticsearchRealmDAO(roleDAO, publisher, client,
props.getIndexMaxResultWindow());
+ }
+
@ConditionalOnMissingBean(name = "elasticsearchAuditConfDAO")
@Bean
public AuditConfDAO auditConfDAO(
diff --git
a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
index 31b4615658..1f28a03757 100644
---
a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
+++
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
@@ -157,12 +157,10 @@ public class ElasticsearchAnySearchDAO extends
AbstractAnySearchDAO {
return noRealm;
});
- realmDAO.findDescendants(
- realm.getFullPath(), null, -1, -1).stream().
- filter(r ->
r.getFullPath().startsWith(base.getFullPath())).
+ realmDAO.findDescendants(realm.getFullPath(),
base.getFullPath()).
forEach(descendant -> queries.add(
new Query.Builder().term(QueryBuilders.term().
-
field("realm").value(descendant.getKey()).build()).
+ field("realm").value(descendant).build()).
build()));
} else {
DynRealm dynRealm = dynRealmDAO.find(realmPath);
diff --git
a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchRealmDAO.java
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchRealmDAO.java
new file mode 100644
index 0000000000..4e618ee430
--- /dev/null
+++
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchRealmDAO.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.ScriptLanguage;
+import co.elastic.clients.elasticsearch._types.ScriptSortType;
+import co.elastic.clients.elasticsearch._types.SearchType;
+import co.elastic.clients.elasticsearch._types.SortOptions;
+import co.elastic.clients.elasticsearch._types.SortOrder;
+import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
+import co.elastic.clients.elasticsearch.core.CountRequest;
+import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.search.Hit;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.ext.elasticsearch.client.ElasticsearchUtils;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.transaction.annotation.Transactional;
+
+public class ElasticsearchRealmDAO extends JPARealmDAO {
+
+ protected static final List<SortOptions> ES_SORT_OPTIONS_REALM = List.of(
+ new SortOptions.Builder().
+ script(s -> s.type(ScriptSortType.Number).
+ script(t -> t.inline(i -> i.lang(ScriptLanguage.Painless).
+ source("doc['fullPath'].value.chars().filter(ch -> ch ==
'/').count()"))).
+ order(SortOrder.Asc)).
+ build());
+
+ protected final ElasticsearchClient client;
+
+ protected final int indexMaxResultWindow;
+
+ public ElasticsearchRealmDAO(
+ final RoleDAO roleDAO,
+ final ApplicationEventPublisher publisher,
+ final ElasticsearchClient client,
+ final int indexMaxResultWindow) {
+
+ super(roleDAO, publisher);
+ this.client = client;
+ this.indexMaxResultWindow = indexMaxResultWindow;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public Realm findByFullPath(final String fullPath) {
+ if (SyncopeConstants.ROOT_REALM.equals(fullPath)) {
+ return getRoot();
+ }
+
+ if (StringUtils.isBlank(fullPath) ||
!PATH_PATTERN.matcher(fullPath).matches()) {
+ throw new MalformedPathException(fullPath);
+ }
+
+ SearchRequest request = new SearchRequest.Builder().
+
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(new Query.Builder().term(QueryBuilders.term().
+ field("fullPath").value(fullPath).build()).build()).
+ size(1).
+ build();
+
+ try {
+ String result = client.search(request,
Void.class).hits().hits().stream().findFirst().
+ map(Hit::id).
+ orElse(null);
+ return find(result);
+ } catch (Exception e) {
+ LOG.error("While searching ES for one match", e);
+ }
+
+ return null;
+ }
+
+ protected List<String> search(final Query query) {
+ SearchRequest request = new SearchRequest.Builder().
+
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(query).
+ sort(ES_SORT_OPTIONS_REALM).
+ build();
+
+ try {
+ return client.search(request, Void.class).hits().hits().stream().
+ map(Hit::id).
+ collect(Collectors.toList());
+ } catch (Exception e) {
+ LOG.error("While searching in Elasticsearch", e);
+ return List.of();
+ }
+ }
+
+ @Override
+ public List<Realm> findByName(final String name) {
+ List<String> result = search(
+ new Query.Builder().term(QueryBuilders.term().
+ field("name").value(name).build()).build());
+ return result.stream().map(this::find).collect(Collectors.toList());
+ }
+
+ @Override
+ public List<Realm> findChildren(final Realm realm) {
+ List<String> result = search(
+ new Query.Builder().term(QueryBuilders.term().
+
field("parent_id").value(realm.getKey()).build()).build());
+ return result.stream().map(this::find).collect(Collectors.toList());
+ }
+
+ protected Query buildDescendantQuery(final String base, final String
keyword) {
+ Query prefix = new
Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new Query.Builder().term(QueryBuilders.term().
+ field("fullPath").value(base).build()).build(),
+ new Query.Builder().regexp(QueryBuilders.regexp().
+
field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base
+ "/.*").
+ build()).build()).build()).build();
+
+ if (keyword == null) {
+ return prefix;
+ }
+
+ return new Query.Builder().bool(QueryBuilders.bool().must(
+ prefix,
+ new Query.Builder().wildcard(QueryBuilders.wildcard().
+ field("name").value(keyword.replace("%",
"*").toLowerCase()).build()).
+ build()).build()).
+ build();
+ }
+
+ @Override
+ public int countDescendants(final String base, final String keyword) {
+ CountRequest request = new CountRequest.Builder().
+
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ query(buildDescendantQuery(base, keyword)).
+ build();
+
+ try {
+ return (int) client.count(request).count();
+ } catch (Exception e) {
+ LOG.error("While counting in Elasticsearch", e);
+ return 0;
+ }
+ }
+
+ @Override
+ public List<Realm> findDescendants(
+ final String base,
+ final String keyword,
+ final int page,
+ final int itemsPerPage) {
+
+ SearchRequest request = new SearchRequest.Builder().
+
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(buildDescendantQuery(base, keyword)).
+ from(itemsPerPage * (page <= 0 ? 0 : page - 1)).
+ size(itemsPerPage < 0 ? indexMaxResultWindow : itemsPerPage).
+ sort(ES_SORT_OPTIONS_REALM).
+ build();
+
+ List<String> result = List.of();
+ try {
+ result = client.search(request, Void.class).hits().hits().stream().
+ map(Hit::id).
+ collect(Collectors.toList());
+ } catch (Exception e) {
+ LOG.error("While searching in Elasticsearch", e);
+ }
+
+ return result.stream().map(this::find).collect(Collectors.toList());
+ }
+
+ @Override
+ public List<String> findDescendants(final String base, final String
prefix) {
+ Query prefixQuery = new
Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new Query.Builder().term(QueryBuilders.term().
+ field("fullPath").value(base).build()).build(),
+ new Query.Builder().prefix(QueryBuilders.prefix().
+
field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(prefix) ? "/" :
prefix + "/").
+ build()).build()).build()).build();
+
+ Query query = new Query.Builder().bool(QueryBuilders.bool().must(
+ buildDescendantQuery(base, (String) null),
+ prefixQuery).build()).
+ build();
+
+ SearchRequest request = new SearchRequest.Builder().
+
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(query).
+ from(0).
+ size(indexMaxResultWindow).
+ sort(ES_SORT_OPTIONS_REALM).
+ build();
+
+ List<String> result = List.of();
+ try {
+ result = client.search(request, Void.class).hits().hits().stream().
+ map(Hit::id).
+ collect(Collectors.toList());
+ } catch (Exception e) {
+ LOG.error("While searching in Elasticsearch", e);
+ }
+ return result;
+ }
+}
diff --git
a/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
b/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
index 02619dc813..03b77935fe 100644
---
a/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
+++
b/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -112,22 +113,19 @@ public class ElasticsearchAnySearchDAOTest {
public void getAdminRealmsFilter4realm() throws IOException {
// 1. mock
Realm root = mock(Realm.class);
- when(root.getKey()).thenReturn("rootKey");
when(root.getFullPath()).thenReturn(SyncopeConstants.ROOT_REALM);
when(realmDAO.findByFullPath(SyncopeConstants.ROOT_REALM)).thenReturn(root);
- when(realmDAO.findDescendants(SyncopeConstants.ROOT_REALM, null, -1,
-1)).thenReturn(List.of(root));
+ when(realmDAO.findDescendants(eq(SyncopeConstants.ROOT_REALM),
anyString())).thenReturn(List.of("rootKey"));
// 2. test
Set<String> adminRealms = Set.of(SyncopeConstants.ROOT_REALM);
Triple<Optional<Query>, Set<String>, Set<String>> filter =
searchDAO.getAdminRealmsFilter(root, true, adminRealms,
AnyTypeKind.USER);
- assertThat(
- new Query.Builder().disMax(QueryBuilders.disMax().queries(
- new
Query.Builder().term(QueryBuilders.term().field("realm").value(
- "rootKey").build()).build()).build()).
- build()).
+ assertThat(new Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new
Query.Builder().term(QueryBuilders.term().field("realm").value("rootKey").build()).
+ build()).build()).build()).
usingRecursiveComparison().isEqualTo(filter.getLeft().get());
assertEquals(Set.of(), filter.getMiddle());
assertEquals(Set.of(), filter.getRight());
diff --git
a/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java
b/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java
index 1756cbe87f..c11a9f4c2d 100644
---
a/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java
+++
b/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java
@@ -28,6 +28,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
@@ -61,6 +62,9 @@ public class ElasticsearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask
@Autowired
protected AnyObjectDAO anyObjectDAO;
+ @Autowired
+ protected RealmDAO realmDAO;
+
protected IndexSettings userSettings() throws IOException {
return indexManager.defaultSettings();
}
@@ -73,6 +77,10 @@ public class ElasticsearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask
return indexManager.defaultSettings();
}
+ protected IndexSettings realmSettings() throws IOException {
+ return indexManager.defaultSettings();
+ }
+
protected IndexSettings auditSettings() throws IOException {
return indexManager.defaultSettings();
}
@@ -89,6 +97,10 @@ public class ElasticsearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask
return indexManager.defaultAnyMapping();
}
+ protected TypeMapping realmMapping() throws IOException {
+ return indexManager.defaultRealmMapping();
+ }
+
protected TypeMapping auditMapping() throws IOException {
return indexManager.defaultAuditMapping();
}
@@ -179,6 +191,31 @@ public class ElasticsearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask
}
}
+ indexManager.createRealmIndex(AuthContextUtils.getDomain(),
realmSettings(), realmMapping());
+
+ int realms = realmDAO.count();
+ String rindex =
ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain());
+ setStatus("Indexing " + realms + " realms under " + rindex +
"...");
+ for (int page = 1; page <= (realms / AnyDAO.DEFAULT_PAGE_SIZE)
+ 1; page++) {
+ BulkRequest.Builder bulkRequest = new
BulkRequest.Builder();
+
+ for (String realm : realmDAO.findAllKeys(page,
AnyDAO.DEFAULT_PAGE_SIZE)) {
+ bulkRequest.operations(op -> op.index(idx -> idx.
+ index(rindex).
+ id(realm).
+
document(utils.document(realmDAO.find(realm)))));
+ }
+
+ try {
+ BulkResponse response =
client.bulk(bulkRequest.build());
+ LOG.debug("Index successfully created for {} [{}/{}]:
{}",
+ rindex, page, AnyDAO.DEFAULT_PAGE_SIZE,
response);
+ } catch (Exception e) {
+ LOG.error("Could not create index for {} [{}/{}]: {}",
+ rindex, page, AnyDAO.DEFAULT_PAGE_SIZE, e);
+ }
+ }
+
indexManager.createAuditIndex(AuthContextUtils.getDomain(),
auditSettings(), auditMapping());
setStatus("Rebuild indexes for domain " +
AuthContextUtils.getDomain() + " successfully completed");
diff --git
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
index 94a80a0717..f100407057 100644
---
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
+++
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
@@ -25,10 +25,10 @@ import org.apache.syncope.common.lib.to.UserRequest;
import org.apache.syncope.common.lib.to.UserRequestForm;
import org.apache.syncope.common.lib.to.WorkflowTaskExecInput;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
-import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.transaction.event.TransactionalEventListener;
@@ -85,7 +85,7 @@ public interface UserRequestHandler {
* @param event delete event
*/
@TransactionalEventListener
- void cancelByUser(AnyLifecycleEvent<Any<?>> event);
+ void cancelByUser(EntityLifecycleEvent<Entity> event);
/**
* Get the form matching the provided task id.
diff --git
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
index 1ab103551e..eb7498e043 100644
---
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
+++
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
@@ -45,13 +45,13 @@ import
org.apache.syncope.core.flowable.support.DomainProcessEngine;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
-import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.workflow.api.WorkflowException;
@@ -266,12 +266,12 @@ public class FlowableUserRequestHandler implements
UserRequestHandler {
}
@Override
- public void cancelByUser(final AnyLifecycleEvent<Any<?>> event) {
+ public void cancelByUser(final EntityLifecycleEvent<Entity> event) {
if (AuthContextUtils.getDomain().equals(event.getDomain())
&& event.getType() == SyncDeltaType.DELETE
- && event.getAny() instanceof User) {
+ && event.getEntity() instanceof User) {
- User user = (User) event.getAny();
+ User user = (User) event.getEntity();
engine.getRuntimeService().createNativeProcessInstanceQuery().
sql(createProcessInstanceQuery(user.getKey()).toString()).
list().forEach(procInst ->
engine.getRuntimeService().deleteProcessInstance(
diff --git
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
index 46fa89f022..8989ce0b3a 100644
---
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
+++
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
@@ -43,7 +43,7 @@ import
org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.SecurityProperties;
@@ -152,7 +152,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User created = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, created,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.CREATE,
created, AuthContextUtils.getDomain()));
engine.getRuntimeService().updateBusinessKey(
procInst.getProcessInstanceId(),
FlowableRuntimeUtils.getWFProcBusinessKey(created.getKey()));
@@ -246,7 +246,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
variables.keySet().forEach(key ->
engine.getRuntimeService().removeVariable(procInstID, key));
engine.getRuntimeService().removeVariable(procInstID,
FlowableRuntimeUtils.USER);
@@ -285,7 +285,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
engine.getRuntimeService().removeVariable(procInstID,
FlowableRuntimeUtils.USER);
engine.getRuntimeService().removeVariable(procInstID,
FlowableRuntimeUtils.WF_EXECUTOR);
@@ -344,7 +344,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
@SuppressWarnings("unchecked")
PropagationByResource<String> propByRes =
engine.getRuntimeService().getVariable(
@@ -377,7 +377,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
@SuppressWarnings("unchecked")
PropagationByResource<String> propByRes =
engine.getRuntimeService().getVariable(
@@ -413,7 +413,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
variables.keySet().forEach(key ->
engine.getRuntimeService().removeVariable(procInstID, key));
engine.getRuntimeService().removeVariable(procInstID,
FlowableRuntimeUtils.USER);
@@ -439,7 +439,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
variables.keySet().forEach(key ->
engine.getRuntimeService().removeVariable(procInstID, key));
engine.getRuntimeService().removeVariable(procInstID,
FlowableRuntimeUtils.USER);
@@ -483,7 +483,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
userDAO.delete(user.getKey());
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, user,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.DELETE,
user, AuthContextUtils.getDomain()));
if
(!engine.getHistoryService().createHistoricProcessInstanceQuery().
processInstanceId(procInstID).list().isEmpty()) {
@@ -505,7 +505,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
User updated = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE,
updated, AuthContextUtils.getDomain()));
engine.getRuntimeService().removeVariable(procInstID,
FlowableRuntimeUtils.TASK);
engine.getRuntimeService().removeVariable(procInstID,
FlowableRuntimeUtils.USER);
@@ -528,7 +528,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
user = userDAO.save(user);
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, user,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, user,
AuthContextUtils.getDomain()));
engine.getRuntimeService().setVariable(
procInstID, FlowableRuntimeUtils.USER_TO,
dataBinder.getUserTO(user, true));
@@ -539,7 +539,7 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
userDAO.delete(user.getKey());
publisher.publishEvent(
- new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, user,
AuthContextUtils.getDomain()));
+ new EntityLifecycleEvent<>(this, SyncDeltaType.DELETE,
user, AuthContextUtils.getDomain()));
if
(!engine.getHistoryService().createHistoricProcessInstanceQuery().
processInstanceId(procInstID).list().isEmpty()) {
diff --git
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexLoader.java
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexLoader.java
index 49f8b21370..e6cde59f4c 100644
---
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexLoader.java
+++
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexLoader.java
@@ -56,6 +56,11 @@ public class OpenSearchIndexLoader implements
SyncopeCoreLoader {
indexManager.defaultSettings(),
indexManager.defaultAnyMapping());
}
+ if (!indexManager.existsRealmIndex(domain)) {
+ indexManager.createRealmIndex(domain,
+ indexManager.defaultSettings(),
indexManager.defaultRealmMapping());
+ }
+
if (!indexManager.existsAuditIndex(domain)) {
indexManager.createAuditIndex(domain,
indexManager.defaultSettings(),
indexManager.defaultAuditMapping());
diff --git
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexManager.java
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexManager.java
index fb5f57581e..ebd016ff22 100644
---
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexManager.java
+++
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchIndexManager.java
@@ -24,11 +24,14 @@ import java.util.List;
import java.util.Map;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.persistence.api.entity.Any;
-import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.opensearch.client.opensearch.OpenSearchClient;
import org.opensearch.client.opensearch._types.OpenSearchException;
+import org.opensearch.client.opensearch._types.Refresh;
import org.opensearch.client.opensearch._types.analysis.CustomNormalizer;
import org.opensearch.client.opensearch._types.analysis.Normalizer;
import org.opensearch.client.opensearch._types.mapping.DynamicTemplate;
@@ -61,7 +64,7 @@ public class OpenSearchIndexManager {
protected final OpenSearchClient client;
- protected final OpenSearchUtils ppenSearchUtils;
+ protected final OpenSearchUtils openSearchUtils;
protected final String numberOfShards;
@@ -74,7 +77,7 @@ public class OpenSearchIndexManager {
final String numberOfReplicas) {
this.client = client;
- this.ppenSearchUtils = ppenSearchUtils;
+ this.openSearchUtils = ppenSearchUtils;
this.numberOfShards = numberOfShards;
this.numberOfReplicas = numberOfReplicas;
}
@@ -85,6 +88,12 @@ public class OpenSearchIndexManager {
value();
}
+ public boolean existsRealmIndex(final String domain) throws IOException {
+ return client.indices().exists(new ExistsRequest.Builder().
+ index(OpenSearchUtils.getRealmIndex(domain)).build()).
+ value();
+ }
+
public boolean existsAuditIndex(final String domain) throws IOException {
return client.indices().exists(new ExistsRequest.Builder().
index(OpenSearchUtils.getAuditIndex(domain)).build()).
@@ -119,6 +128,19 @@ public class OpenSearchIndexManager {
build();
}
+ public TypeMapping defaultRealmMapping() throws IOException {
+ return new TypeMapping.Builder().
+ dynamicTemplates(List.of(Map.of(
+ "strings",
+ new DynamicTemplate.Builder().
+ matchMappingType("string").
+ mapping(new Property.Builder().
+ keyword(new
KeywordProperty.Builder().normalizer("string_lowercase").build()).
+ build()).
+ build()))).
+ build();
+ }
+
public TypeMapping defaultAuditMapping() throws IOException {
return new TypeMapping.Builder().
dynamicTemplates(List.of(Map.of(
@@ -198,6 +220,45 @@ public class OpenSearchIndexManager {
LOG.debug("Successfully removed {}: {}",
OpenSearchUtils.getAnyIndex(domain, kind), response);
}
+ protected CreateIndexResponse doCreateRealmIndex(
+ final String domain,
+ final IndexSettings settings,
+ final TypeMapping mappings) throws IOException {
+
+ return client.indices().create(
+ new CreateIndexRequest.Builder().
+ index(OpenSearchUtils.getRealmIndex(domain)).
+ settings(settings).
+ mappings(mappings).
+ build());
+ }
+
+ public void createRealmIndex(
+ final String domain,
+ final IndexSettings settings,
+ final TypeMapping mappings)
+ throws IOException {
+
+ try {
+ CreateIndexResponse response = doCreateRealmIndex(domain,
settings, mappings);
+
+ LOG.debug("Successfully created realm index {}: {}",
+ OpenSearchUtils.getRealmIndex(domain), response);
+ } catch (OpenSearchException e) {
+ LOG.debug("Could not create realm index {} because it already
exists",
+ OpenSearchUtils.getRealmIndex(domain), e);
+
+ removeRealmIndex(domain);
+ doCreateRealmIndex(domain, settings, mappings);
+ }
+ }
+
+ public void removeRealmIndex(final String domain) throws IOException {
+ DeleteIndexResponse response = client.indices().delete(
+ new
DeleteIndexRequest.Builder().index(OpenSearchUtils.getRealmIndex(domain)).build());
+ LOG.debug("Successfully removed {}: {}",
OpenSearchUtils.getRealmIndex(domain), response);
+ }
+
protected CreateIndexResponse doCreateAuditIndex(
final String domain,
final IndexSettings settings,
@@ -238,37 +299,60 @@ public class OpenSearchIndexManager {
}
@TransactionalEventListener
- public void any(final AnyLifecycleEvent<Any<?>> event) throws IOException {
- LOG.debug("About to {} index for {}", event.getType().name(),
event.getAny());
-
- if (event.getType() == SyncDeltaType.DELETE) {
- DeleteRequest request = new DeleteRequest.Builder().index(
- OpenSearchUtils.getAnyIndex(event.getDomain(),
event.getAny().getType().getKind())).
- id(event.getAny().getKey()).
- build();
- DeleteResponse response = client.delete(request);
- LOG.debug("Index successfully deleted for {}[{}]: {}",
- event.getAny().getType().getKind(),
event.getAny().getKey(), response);
- } else {
- IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
- index(OpenSearchUtils.getAnyIndex(event.getDomain(),
event.getAny().getType().getKind())).
- id(event.getAny().getKey()).
- document(ppenSearchUtils.document(event.getAny())).
- build();
- IndexResponse response = client.index(request);
- LOG.debug("Index successfully created or updated for {}: {}",
event.getAny(), response);
+ public void entity(final EntityLifecycleEvent<Entity> event) throws
IOException {
+ LOG.debug("About to {} index for {}", event.getType().name(),
event.getEntity());
+
+ if (event.getEntity() instanceof Any) {
+ Any<?> any = (Any<?>) event.getEntity();
+
+ if (event.getType() == SyncDeltaType.DELETE) {
+ DeleteRequest request = new DeleteRequest.Builder().index(
+ OpenSearchUtils.getAnyIndex(event.getDomain(),
any.getType().getKind())).
+ id(any.getKey()).
+ build();
+ DeleteResponse response = client.delete(request);
+ LOG.debug("Index successfully deleted for {}[{}]: {}",
+ any.getType().getKind(), any.getKey(), response);
+ } else {
+ IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
+ index(OpenSearchUtils.getAnyIndex(event.getDomain(),
any.getType().getKind())).
+ id(any.getKey()).
+ document(openSearchUtils.document(any)).
+ build();
+ IndexResponse response = client.index(request);
+ LOG.debug("Index successfully created or updated for {}: {}",
any, response);
+ }
+ } else if (event.getEntity() instanceof Realm) {
+ Realm realm = (Realm) event.getEntity();
+
+ if (event.getType() == SyncDeltaType.DELETE) {
+ DeleteRequest request = new DeleteRequest.Builder().
+
index(OpenSearchUtils.getRealmIndex(event.getDomain())).
+ id(realm.getKey()).
+ refresh(Refresh.True).
+ build();
+ DeleteResponse response = client.delete(request);
+ LOG.debug("Index successfully deleted for {}: {}", realm,
response);
+ } else {
+ IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
+
index(OpenSearchUtils.getRealmIndex(event.getDomain())).
+ id(realm.getKey()).
+ document(openSearchUtils.document(realm)).
+ refresh(Refresh.True).
+ build();
+ IndexResponse response = client.index(request);
+ LOG.debug("Index successfully created or updated for {}: {}",
realm, response);
+ }
}
}
- public void audit(final String domain, final long instant, final JsonNode
message)
- throws IOException {
-
+ public void audit(final String domain, final long instant, final JsonNode
message) throws IOException {
LOG.debug("About to audit");
IndexRequest<Map<String, Object>> request = new
IndexRequest.Builder<Map<String, Object>>().
index(OpenSearchUtils.getAuditIndex(domain)).
id(SecureRandomUtils.generateRandomUUID().toString()).
- document(ppenSearchUtils.document(instant, message, domain)).
+ document(openSearchUtils.document(instant, message, domain)).
build();
IndexResponse response = client.index(request);
diff --git
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
index 7329456237..2c6902c1dd 100644
---
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
+++
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
@@ -38,6 +38,7 @@ import
org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.Privilege;
+import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -52,6 +53,10 @@ public class OpenSearchUtils {
return domain.toLowerCase() + '_' + kind.name().toLowerCase();
}
+ public static String getRealmIndex(final String domain) {
+ return domain.toLowerCase() + "_realm";
+ }
+
public static String getAuditIndex(final String domain) {
return domain.toLowerCase() + "_audit";
}
@@ -121,7 +126,7 @@ public class OpenSearchUtils {
builder.put("relationships", relationships);
builder.put("relationshipTypes", relationshipTypes);
- OpenSearchUtils.this.customizeDocument(builder, anyObject);
+ customizeDocument(builder, anyObject);
} else if (any instanceof Group) {
Group group = ((Group) any);
builder.put("name", group.getName());
@@ -137,7 +142,7 @@ public class OpenSearchUtils {
members.addAll(groupDAO.findADynMembers(group));
builder.put("members", members);
- OpenSearchUtils.this.customizeDocument(builder, group);
+ customizeDocument(builder, group);
} else if (any instanceof User) {
User user = ((User) any);
builder.put("username", user.getUsername());
@@ -193,6 +198,21 @@ public class OpenSearchUtils {
protected void customizeDocument(final Map<String, Object> builder, final
User user) {
}
+ public Map<String, Object> document(final Realm realm) {
+ Map<String, Object> builder = new HashMap<>();
+ builder.put("id", realm.getKey());
+ builder.put("name", realm.getName());
+ builder.put("parent_id", realm.getParent() == null ? null :
realm.getParent().getKey());
+ builder.put("fullPath", realm.getFullPath());
+
+ customizeDocument(builder, realm);
+
+ return builder;
+ }
+
+ protected void customizeDocument(final Map<String, Object> builder, final
Realm realm) {
+ }
+
public Map<String, Object> document(
final long instant,
final JsonNode message,
diff --git
a/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OpenSearchPersistenceContext.java
b/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OpenSearchPersistenceContext.java
index b5fbebdb73..8725d1e2e8 100644
---
a/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OpenSearchPersistenceContext.java
+++
b/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OpenSearchPersistenceContext.java
@@ -26,14 +26,17 @@ import
org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.jpa.dao.OpenSearchAnySearchDAO;
import org.apache.syncope.core.persistence.jpa.dao.OpenSearchAuditConfDAO;
+import org.apache.syncope.core.persistence.jpa.dao.OpenSearchRealmDAO;
import org.apache.syncope.ext.opensearch.client.OpenSearchProperties;
import org.opensearch.client.opensearch.OpenSearchClient;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@@ -70,6 +73,17 @@ public class OpenSearchPersistenceContext {
props.getIndexMaxResultWindow());
}
+ @ConditionalOnMissingBean(name = "openSearchRealmDAO")
+ @Bean
+ public RealmDAO realmDAO(
+ final @Lazy RoleDAO roleDAO,
+ final ApplicationEventPublisher publisher,
+ final OpenSearchProperties props,
+ final OpenSearchClient client) {
+
+ return new OpenSearchRealmDAO(roleDAO, publisher, client,
props.getIndexMaxResultWindow());
+ }
+
@ConditionalOnMissingBean(name = "openSearchAuditConfDAO")
@Bean
public AuditConfDAO auditConfDAO(
diff --git
a/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAO.java
b/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAO.java
index 447e168e10..b138a3a977 100644
---
a/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAO.java
+++
b/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAO.java
@@ -157,12 +157,10 @@ public class OpenSearchAnySearchDAO extends
AbstractAnySearchDAO {
return noRealm;
});
- realmDAO.findDescendants(
- realm.getFullPath(), null, -1, -1).stream().
- filter(r ->
r.getFullPath().startsWith(base.getFullPath())).
+ realmDAO.findDescendants(realm.getFullPath(),
base.getFullPath()).
forEach(descendant -> queries.add(
new Query.Builder().term(QueryBuilders.term().
-
field("realm").value(FieldValue.of(descendant.getKey())).build()).
+
field("realm").value(FieldValue.of(descendant)).build()).
build()));
} else {
DynRealm dynRealm = dynRealmDAO.find(realmPath);
diff --git
a/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchRealmDAO.java
b/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchRealmDAO.java
new file mode 100644
index 0000000000..2fc2de406f
--- /dev/null
+++
b/ext/opensearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchRealmDAO.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.ext.opensearch.client.OpenSearchUtils;
+import org.opensearch.client.opensearch.OpenSearchClient;
+import org.opensearch.client.opensearch._types.FieldValue;
+import org.opensearch.client.opensearch._types.ScriptSortType;
+import org.opensearch.client.opensearch._types.SearchType;
+import org.opensearch.client.opensearch._types.SortOptions;
+import org.opensearch.client.opensearch._types.SortOrder;
+import org.opensearch.client.opensearch._types.query_dsl.Query;
+import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders;
+import org.opensearch.client.opensearch.core.CountRequest;
+import org.opensearch.client.opensearch.core.SearchRequest;
+import org.opensearch.client.opensearch.core.search.Hit;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.transaction.annotation.Transactional;
+
+public class OpenSearchRealmDAO extends JPARealmDAO {
+
+ protected static final List<SortOptions> ES_SORT_OPTIONS_REALM = List.of(
+ new SortOptions.Builder().
+ script(s -> s.type(ScriptSortType.Number).
+ script(t -> t.inline(i -> i.lang("painless").
+ source("doc['fullPath'].value.chars().filter(ch -> ch ==
'/').count()"))).
+ order(SortOrder.Asc)).
+ build());
+
+ protected final OpenSearchClient client;
+
+ protected final int indexMaxResultWindow;
+
+ public OpenSearchRealmDAO(
+ final RoleDAO roleDAO,
+ final ApplicationEventPublisher publisher,
+ final OpenSearchClient client,
+ final int indexMaxResultWindow) {
+
+ super(roleDAO, publisher);
+ this.client = client;
+ this.indexMaxResultWindow = indexMaxResultWindow;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public Realm findByFullPath(final String fullPath) {
+ if (SyncopeConstants.ROOT_REALM.equals(fullPath)) {
+ return getRoot();
+ }
+
+ if (StringUtils.isBlank(fullPath) ||
!PATH_PATTERN.matcher(fullPath).matches()) {
+ throw new MalformedPathException(fullPath);
+ }
+
+ SearchRequest request = new SearchRequest.Builder().
+
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(new Query.Builder().term(QueryBuilders.term().
+
field("fullPath").value(FieldValue.of(fullPath)).build()).build()).
+ size(1).
+ build();
+
+ try {
+ String result = client.search(request,
Void.class).hits().hits().stream().findFirst().
+ map(Hit::id).
+ orElse(null);
+ return find(result);
+ } catch (Exception e) {
+ LOG.error("While searching ES for one match", e);
+ }
+
+ return null;
+ }
+
+ protected List<String> search(final Query query) {
+ SearchRequest request = new SearchRequest.Builder().
+
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(query).
+ sort(ES_SORT_OPTIONS_REALM).
+ build();
+
+ try {
+ return client.search(request, Void.class).hits().hits().stream().
+ map(Hit::id).
+ collect(Collectors.toList());
+ } catch (Exception e) {
+ LOG.error("While searching in OpenSearch", e);
+ return List.of();
+ }
+ }
+
+ @Override
+ public List<Realm> findByName(final String name) {
+ List<String> result = search(
+ new Query.Builder().term(QueryBuilders.term().
+
field("name").value(FieldValue.of(name)).build()).build());
+ return result.stream().map(this::find).collect(Collectors.toList());
+ }
+
+ @Override
+ public List<Realm> findChildren(final Realm realm) {
+ List<String> result = search(
+ new Query.Builder().term(QueryBuilders.term().
+
field("parent_id").value(FieldValue.of(realm.getKey())).build()).build());
+ return result.stream().map(this::find).collect(Collectors.toList());
+ }
+
+ protected Query buildDescendantQuery(final String base, final String
keyword) {
+ Query prefix = new
Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new Query.Builder().term(QueryBuilders.term().
+
field("fullPath").value(FieldValue.of(base)).build()).build(),
+ new Query.Builder().regexp(QueryBuilders.regexp().
+
field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base
+ "/.*").
+ build()).build()).build()).build();
+
+ if (keyword == null) {
+ return prefix;
+ }
+
+ return new Query.Builder().bool(QueryBuilders.bool().must(
+ prefix,
+ new Query.Builder().wildcard(QueryBuilders.wildcard().
+ field("name").value(keyword.replace("%",
"*").toLowerCase()).build()).
+ build()).build()).
+ build();
+ }
+
+ @Override
+ public int countDescendants(final String base, final String keyword) {
+ CountRequest request = new CountRequest.Builder().
+
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ query(buildDescendantQuery(base, keyword)).
+ build();
+
+ try {
+ return (int) client.count(request).count();
+ } catch (Exception e) {
+ LOG.error("While counting in OpenSearch", e);
+ return 0;
+ }
+ }
+
+ @Override
+ public List<Realm> findDescendants(
+ final String base,
+ final String keyword,
+ final int page,
+ final int itemsPerPage) {
+
+ SearchRequest request = new SearchRequest.Builder().
+
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(buildDescendantQuery(base, keyword)).
+ from(itemsPerPage * (page <= 0 ? 0 : page - 1)).
+ size(itemsPerPage < 0 ? indexMaxResultWindow : itemsPerPage).
+ sort(ES_SORT_OPTIONS_REALM).
+ build();
+
+ List<String> result = List.of();
+ try {
+ result = client.search(request, Void.class).hits().hits().stream().
+ map(Hit::id).
+ collect(Collectors.toList());
+ } catch (Exception e) {
+ LOG.error("While searching in OpenSearch", e);
+ }
+
+ return result.stream().map(this::find).collect(Collectors.toList());
+ }
+
+ @Override
+ public List<String> findDescendants(final String base, final String
prefix) {
+ Query prefixQuery = new
Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new Query.Builder().term(QueryBuilders.term().
+
field("fullPath").value(FieldValue.of(base)).build()).build(),
+ new Query.Builder().prefix(QueryBuilders.prefix().
+
field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(prefix) ? "/" :
prefix + "/").
+ build()).build()).build()).build();
+
+ Query query = new Query.Builder().bool(QueryBuilders.bool().must(
+ buildDescendantQuery(base, (String) null),
+ prefixQuery).build()).
+ build();
+
+ SearchRequest request = new SearchRequest.Builder().
+
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
+ searchType(SearchType.QueryThenFetch).
+ query(query).
+ from(0).
+ size(indexMaxResultWindow).
+ sort(ES_SORT_OPTIONS_REALM).
+ build();
+
+ List<String> result = List.of();
+ try {
+ result = client.search(request, Void.class).hits().hits().stream().
+ map(Hit::id).
+ collect(Collectors.toList());
+ } catch (Exception e) {
+ LOG.error("While searching in OpenSearch", e);
+ }
+ return result;
+ }
+}
diff --git
a/ext/opensearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAOTest.java
b/ext/opensearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAOTest.java
index ef6deea7ff..2c5a5a113e 100644
---
a/ext/opensearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAOTest.java
+++
b/ext/opensearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/OpenSearchAnySearchDAOTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -113,22 +114,19 @@ public class OpenSearchAnySearchDAOTest {
public void getAdminRealmsFilter4realm() throws IOException {
// 1. mock
Realm root = mock(Realm.class);
- when(root.getKey()).thenReturn("rootKey");
when(root.getFullPath()).thenReturn(SyncopeConstants.ROOT_REALM);
when(realmDAO.findByFullPath(SyncopeConstants.ROOT_REALM)).thenReturn(root);
- when(realmDAO.findDescendants(SyncopeConstants.ROOT_REALM, null, -1,
-1)).thenReturn(List.of(root));
+ when(realmDAO.findDescendants(eq(SyncopeConstants.ROOT_REALM),
anyString())).thenReturn(List.of("rootKey"));
// 2. test
Set<String> adminRealms = Set.of(SyncopeConstants.ROOT_REALM);
Triple<Optional<Query>, Set<String>, Set<String>> filter =
searchDAO.getAdminRealmsFilter(root, true, adminRealms,
AnyTypeKind.USER);
- assertThat(
- new Query.Builder().disMax(QueryBuilders.disMax().queries(
- new
Query.Builder().term(QueryBuilders.term().field("realm").value(
-
FieldValue.of("rootKey")).build()).build()).build()).
- build()).
+ assertThat(new Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new
Query.Builder().term(QueryBuilders.term().field("realm").value(FieldValue.of("rootKey")).build()).
+ build()).build()).build()).
usingRecursiveComparison().isEqualTo(filter.getLeft().get());
assertEquals(Set.of(), filter.getMiddle());
assertEquals(Set.of(), filter.getRight());
diff --git
a/ext/opensearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/OpenSearchReindex.java
b/ext/opensearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/OpenSearchReindex.java
index 02273c540a..782c8de24a 100644
---
a/ext/opensearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/OpenSearchReindex.java
+++
b/ext/opensearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/OpenSearchReindex.java
@@ -23,6 +23,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
@@ -61,6 +62,9 @@ public class OpenSearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask> {
@Autowired
protected AnyObjectDAO anyObjectDAO;
+ @Autowired
+ protected RealmDAO realmDAO;
+
protected IndexSettings userSettings() throws IOException {
return indexManager.defaultSettings();
}
@@ -73,6 +77,10 @@ public class OpenSearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask> {
return indexManager.defaultSettings();
}
+ protected IndexSettings realmSettings() throws IOException {
+ return indexManager.defaultSettings();
+ }
+
protected IndexSettings auditSettings() throws IOException {
return indexManager.defaultSettings();
}
@@ -89,6 +97,10 @@ public class OpenSearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask> {
return indexManager.defaultAnyMapping();
}
+ protected TypeMapping realmMapping() throws IOException {
+ return indexManager.defaultRealmMapping();
+ }
+
protected TypeMapping auditMapping() throws IOException {
return indexManager.defaultAuditMapping();
}
@@ -179,6 +191,31 @@ public class OpenSearchReindex extends
AbstractSchedTaskJobDelegate<SchedTask> {
}
}
+ indexManager.createRealmIndex(AuthContextUtils.getDomain(),
realmSettings(), realmMapping());
+
+ int realms = realmDAO.count();
+ String rindex =
OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain());
+ setStatus("Indexing " + realms + " realms under " + rindex +
"...");
+ for (int page = 1; page <= (realms / AnyDAO.DEFAULT_PAGE_SIZE)
+ 1; page++) {
+ BulkRequest.Builder bulkRequest = new
BulkRequest.Builder();
+
+ for (String realm : realmDAO.findAllKeys(page,
AnyDAO.DEFAULT_PAGE_SIZE)) {
+ bulkRequest.operations(op -> op.index(idx -> idx.
+ index(rindex).
+ id(realm).
+
document(utils.document(realmDAO.find(realm)))));
+ }
+
+ try {
+ BulkResponse response =
client.bulk(bulkRequest.build());
+ LOG.debug("Index successfully created for {} [{}/{}]:
{}",
+ rindex, page, AnyDAO.DEFAULT_PAGE_SIZE,
response);
+ } catch (Exception e) {
+ LOG.error("Could not create index for {} [{}/{}]: {}",
+ rindex, page, AnyDAO.DEFAULT_PAGE_SIZE, e);
+ }
+ }
+
indexManager.createAuditIndex(AuthContextUtils.getDomain(),
auditSettings(), auditMapping());
setStatus("Rebuild indexes for domain " +
AuthContextUtils.getDomain() + " successfully completed");
diff --git
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index 6d06f55848..196db1df6a 100644
---
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -214,8 +214,6 @@ public class ITImplementationLookup implements
ImplementationLookup {
private final OpenSearchInit openSearchInit;
- private boolean loaded;
-
public ITImplementationLookup(
final UserWorkflowAdapter uwf,
final AnySearchDAO anySearchDAO,
@@ -237,11 +235,6 @@ public class ITImplementationLookup implements
ImplementationLookup {
@Override
public void load(final String domain, final DataSource datasource) {
- if (loaded) {
- LOG.debug("Already loaded, nothing to do");
- return;
- }
-
// in case the Flowable extension is enabled, enable modifications for
test users
if (enableFlowableForTestUsers != null &&
AopUtils.getTargetClass(uwf).getName().contains("Flowable")) {
AuthContextUtils.callAsAdmin(domain, () -> {
@@ -265,8 +258,6 @@ public class ITImplementationLookup implements
ImplementationLookup {
return null;
});
}
-
- loaded = true;
}
@Override
diff --git a/src/main/asciidoc/reference-guide/concepts/extensions.adoc
b/src/main/asciidoc/reference-guide/concepts/extensions.adoc
index 9464c8f551..716fab5812 100644
--- a/src/main/asciidoc/reference-guide/concepts/extensions.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/extensions.adoc
@@ -101,8 +101,8 @@ This extension adds features to all components and layers
that are available, an
==== Elasticsearch
-This extension provides an alternate internal search engine for
<<users-groups-and-any-objects>> and <<audit-events>>,
-requiring an external https://www.elastic.co/[Elasticsearch^] cluster.
+This extension provides an alternate internal search engine for
<<users-groups-and-any-objects>>,<<realms>> and
+<<audit-events>>, requiring an external
https://www.elastic.co/[Elasticsearch^] cluster.
[WARNING]
This extension supports Elasticsearch server versions starting from 8.x.
@@ -126,8 +126,8 @@ endif::[]
==== OpenSearch
-This extension provides an alternate internal search engine for
<<users-groups-and-any-objects>> and <<audit-events>>,
-requiring an external https://opensearch.org/[OpenSearch^] cluster.
+This extension provides an alternate internal search engine for
<<users-groups-and-any-objects>>,<<realms>> and
+<<audit-events>>, requiring an external https://opensearch.org/[OpenSearch^]
cluster.
[TIP]
As search operations are central for different aspects of the
<<provisioning,provisioning process>>, the global