View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.persistence.jpa.dao;
20  
21  import java.time.OffsetDateTime;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.stream.Collectors;
29  import javax.persistence.NoResultException;
30  import javax.persistence.Query;
31  import javax.persistence.TypedQuery;
32  import org.apache.commons.lang3.tuple.Pair;
33  import org.apache.syncope.common.lib.types.AnyTypeKind;
34  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
35  import org.apache.syncope.core.persistence.api.dao.AnyDAO;
36  import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
37  import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
38  import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
39  import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
40  import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
41  import org.apache.syncope.core.persistence.api.dao.GroupDAO;
42  import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
43  import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
44  import org.apache.syncope.core.persistence.api.dao.UserDAO;
45  import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
46  import org.apache.syncope.core.persistence.api.entity.AnyType;
47  import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
48  import org.apache.syncope.core.persistence.api.entity.AnyUtils;
49  import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
50  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
51  import org.apache.syncope.core.persistence.api.entity.Realm;
52  import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
53  import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
54  import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
55  import org.apache.syncope.core.persistence.api.entity.group.Group;
56  import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
57  import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
58  import org.apache.syncope.core.persistence.api.entity.user.UMembership;
59  import org.apache.syncope.core.persistence.api.entity.user.User;
60  import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
61  import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
62  import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership;
63  import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership;
64  import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
65  import org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension;
66  import org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership;
67  import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership;
68  import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
69  import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
70  import org.apache.syncope.core.spring.security.AuthContextUtils;
71  import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
72  import org.identityconnectors.framework.common.objects.SyncDeltaType;
73  import org.springframework.context.ApplicationEventPublisher;
74  import org.springframework.transaction.annotation.Transactional;
75  
76  public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
77  
78      public static final String UDYNMEMB_TABLE = "UDynGroupMembers";
79  
80      public static final String ADYNMEMB_TABLE = "ADynGroupMembers";
81  
82      protected final ApplicationEventPublisher publisher;
83  
84      protected final AnyMatchDAO anyMatchDAO;
85  
86      protected final PlainAttrDAO plainAttrDAO;
87  
88      protected final UserDAO userDAO;
89  
90      protected final AnyObjectDAO anyObjectDAO;
91  
92      protected final AnySearchDAO anySearchDAO;
93  
94      protected final SearchCondVisitor searchCondVisitor;
95  
96      public JPAGroupDAO(
97              final AnyUtilsFactory anyUtilsFactory,
98              final ApplicationEventPublisher publisher,
99              final PlainSchemaDAO plainSchemaDAO,
100             final DerSchemaDAO derSchemaDAO,
101             final DynRealmDAO dynRealmDAO,
102             final AnyMatchDAO anyMatchDAO,
103             final PlainAttrDAO plainAttrDAO,
104             final UserDAO userDAO,
105             final AnyObjectDAO anyObjectDAO,
106             final AnySearchDAO searchDAO,
107             final SearchCondVisitor searchCondVisitor) {
108 
109         super(anyUtilsFactory, plainSchemaDAO, derSchemaDAO, dynRealmDAO);
110         this.publisher = publisher;
111         this.anyMatchDAO = anyMatchDAO;
112         this.plainAttrDAO = plainAttrDAO;
113         this.userDAO = userDAO;
114         this.anyObjectDAO = anyObjectDAO;
115         this.anySearchDAO = searchDAO;
116         this.searchCondVisitor = searchCondVisitor;
117     }
118 
119     @Override
120     protected AnyUtils init() {
121         return anyUtilsFactory.getInstance(AnyTypeKind.GROUP);
122     }
123 
124     @Transactional(readOnly = true)
125     @Override
126     public String findKey(final String name) {
127         Query query = entityManager().createNativeQuery("SELECT id FROM " + JPAGroup.TABLE + " WHERE name=?");
128         query.setParameter(1, name);
129 
130         String key = null;
131 
132         for (Object resultKey : query.getResultList()) {
133             key = resultKey instanceof Object[]
134                     ? (String) ((Object[]) resultKey)[0]
135                     : ((String) resultKey);
136         }
137 
138         return key;
139     }
140 
141     @Transactional(readOnly = true)
142     @Override
143     public OffsetDateTime findLastChange(final String key) {
144         return findLastChange(key, JPAGroup.TABLE);
145     }
146 
147     @Override
148     public int count() {
149         Query query = entityManager().createQuery(
150                 "SELECT COUNT(e) FROM " + anyUtils().anyClass().getSimpleName() + " e");
151         return ((Number) query.getSingleResult()).intValue();
152     }
153 
154     @Override
155     public Map<String, Integer> countByRealm() {
156         Query query = entityManager().createQuery(
157                 "SELECT e.realm, COUNT(e) FROM " + anyUtils().anyClass().getSimpleName() + " e GROUP BY e.realm");
158 
159         @SuppressWarnings("unchecked")
160         List<Object[]> results = query.getResultList();
161         return results.stream().collect(Collectors.toMap(
162                 result -> ((Realm) result[0]).getFullPath(),
163                 result -> ((Number) result[1]).intValue()));
164     }
165 
166     @Transactional(readOnly = true)
167     @Override
168     public void securityChecks(
169             final Set<String> authRealms,
170             final String key,
171             final String realm) {
172 
173         // 1. check if AuthContextUtils.getUsername() is owner of the group, or
174         // if group is in Realm (or descendants) for which AuthContextUtils.getUsername() owns entitlement
175         boolean authorized = authRealms.stream().anyMatch(authRealm -> realm.startsWith(authRealm)
176                 || authRealm.equals(RealmUtils.getGroupOwnerRealm(realm, key)));
177 
178         // 2. check if groups is in at least one DynRealm for which AuthContextUtils.getUsername() owns entitlement
179         if (!authorized) {
180             authorized = findDynRealms(key).stream().anyMatch(authRealms::contains);
181         }
182 
183         if (authRealms.isEmpty() || !authorized) {
184             throw new DelegatedAdministrationException(realm, AnyTypeKind.GROUP.name(), key);
185         }
186     }
187 
188     @Override
189     protected void securityChecks(final Group group) {
190         Set<String> authRealms = AuthContextUtils.getAuthorizations().
191                 getOrDefault(IdRepoEntitlement.GROUP_READ, Set.of());
192 
193         securityChecks(authRealms, group.getKey(), group.getRealm().getFullPath());
194     }
195 
196     @Override
197     public Group findByName(final String name) {
198         TypedQuery<Group> query = entityManager().createQuery(
199                 "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e WHERE e.name = :name", Group.class);
200         query.setParameter("name", name);
201 
202         Group result = null;
203         try {
204             result = query.getSingleResult();
205         } catch (NoResultException e) {
206             LOG.debug("No group found with name {}", name, e);
207         }
208 
209         return result;
210     }
211 
212     @Override
213     public List<String> findKeysByNamePattern(final String pattern) {
214         Query query = entityManager().createNativeQuery(
215                 "SELECT id FROM " + JPAGroup.TABLE + " WHERE LOWER(name) LIKE LOWER(?1)");
216         query.setParameter(1, pattern);
217 
218         @SuppressWarnings("unchecked")
219         List<Object> raw = query.getResultList();
220         return raw.stream().map(Object::toString).collect(Collectors.toList());
221     }
222 
223     @Transactional(readOnly = true)
224     @Override
225     public List<Group> findOwnedByUser(final String userKey) {
226         User owner = userDAO.find(userKey);
227         if (owner == null) {
228             return List.of();
229         }
230 
231         StringBuilder queryString = new StringBuilder("SELECT e FROM ").append(anyUtils().anyClass().getSimpleName())
232                 .append(" e WHERE e.userOwner=:owner ");
233         userDAO.findAllGroupKeys(owner).forEach(groupKey -> queryString
234                 .append("OR e.groupOwner.id='").append(groupKey).append("' "));
235 
236         TypedQuery<Group> query = entityManager().createQuery(queryString.toString(), Group.class);
237         query.setParameter("owner", owner);
238 
239         return query.getResultList();
240     }
241 
242     @Transactional(readOnly = true)
243     @Override
244     public List<Group> findOwnedByGroup(final String groupKey) {
245         Group owner = find(groupKey);
246         if (owner == null) {
247             return List.of();
248         }
249 
250         TypedQuery<Group> query = entityManager().createQuery(
251                 "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e WHERE e.groupOwner=:owner", Group.class);
252         query.setParameter("owner", owner);
253 
254         return query.getResultList();
255     }
256 
257     @Override
258     public List<AMembership> findAMemberships(final Group group) {
259         TypedQuery<AMembership> query = entityManager().createQuery(
260                 "SELECT e FROM " + JPAAMembership.class.getSimpleName() + " e WHERE e.rightEnd=:group",
261                 AMembership.class);
262         query.setParameter("group", group);
263 
264         return query.getResultList();
265     }
266 
267     @Override
268     public List<UMembership> findUMemberships(final Group group) {
269         TypedQuery<UMembership> query = entityManager().createQuery(
270                 "SELECT e FROM " + JPAUMembership.class.getSimpleName() + " e WHERE e.rightEnd=:group",
271                 UMembership.class);
272         query.setParameter("group", group);
273 
274         return query.getResultList();
275     }
276 
277     @Override
278     public List<Group> findAll(final int page, final int itemsPerPage) {
279         TypedQuery<Group> query = entityManager().createQuery(
280                 "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e ORDER BY e.id", Group.class);
281         query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
282         query.setMaxResults(itemsPerPage);
283 
284         return query.getResultList();
285     }
286 
287     @Override
288     public List<String> findAllKeys(final int page, final int itemsPerPage) {
289         return findAllKeys(JPAGroup.TABLE, page, itemsPerPage);
290     }
291 
292     protected SearchCond buildDynMembershipCond(final String baseCondFIQL) {
293         return SearchCondConverter.convert(searchCondVisitor, baseCondFIQL);
294     }
295 
296     @Override
297     public Group saveAndRefreshDynMemberships(final Group group) {
298         Group merged = save(group);
299 
300         // refresh dynamic memberships
301         clearUDynMembers(merged);
302         if (merged.getUDynMembership() != null) {
303             SearchCond cond = buildDynMembershipCond(merged.getUDynMembership().getFIQLCond());
304             int count = anySearchDAO.count(
305                     merged.getRealm(), true, Set.of(merged.getRealm().getFullPath()), cond, AnyTypeKind.USER);
306             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
307                 List<User> matching = anySearchDAO.search(
308                         merged.getRealm(),
309                         true,
310                         Set.of(merged.getRealm().getFullPath()),
311                         cond,
312                         page,
313                         AnyDAO.DEFAULT_PAGE_SIZE,
314                         List.of(),
315                         AnyTypeKind.USER);
316 
317                 matching.forEach(user -> {
318                     Query insert = entityManager().createNativeQuery("INSERT INTO " + UDYNMEMB_TABLE + " VALUES(?, ?)");
319                     insert.setParameter(1, user.getKey());
320                     insert.setParameter(2, merged.getKey());
321                     insert.executeUpdate();
322 
323                     publisher.publishEvent(
324                             new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, user, AuthContextUtils.getDomain()));
325                 });
326             }
327         }
328         clearADynMembers(merged);
329         merged.getADynMemberships().forEach(memb -> {
330             SearchCond cond = buildDynMembershipCond(memb.getFIQLCond());
331             int count = anySearchDAO.count(
332                     merged.getRealm(), true, Set.of(merged.getRealm().getFullPath()), cond, AnyTypeKind.ANY_OBJECT);
333             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
334                 List<AnyObject> matching = anySearchDAO.search(
335                         merged.getRealm(),
336                         true,
337                         Set.of(merged.getRealm().getFullPath()),
338                         cond,
339                         page,
340                         AnyDAO.DEFAULT_PAGE_SIZE,
341                         List.of(),
342                         AnyTypeKind.ANY_OBJECT);
343 
344                 matching.forEach(any -> {
345                     Query insert = entityManager().createNativeQuery(
346                             "INSERT INTO " + ADYNMEMB_TABLE + " VALUES(?, ?, ?)");
347                     insert.setParameter(1, any.getType().getKey());
348                     insert.setParameter(2, any.getKey());
349                     insert.setParameter(3, merged.getKey());
350                     insert.executeUpdate();
351 
352                     publisher.publishEvent(
353                             new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, any, AuthContextUtils.getDomain()));
354                 });
355             }
356         });
357 
358         dynRealmDAO.refreshDynMemberships(merged);
359 
360         return merged;
361     }
362 
363     @Override
364     public void delete(final Group group) {
365         dynRealmDAO.removeDynMemberships(group.getKey());
366 
367         findAMemberships(group).forEach(membership -> {
368             AnyObject leftEnd = membership.getLeftEnd();
369             leftEnd.remove(membership);
370             membership.setRightEnd(null);
371             leftEnd.getPlainAttrs(membership).forEach(attr -> {
372                 leftEnd.remove(attr);
373                 attr.setOwner(null);
374                 attr.setMembership(null);
375                 plainAttrDAO.delete(attr);
376             });
377 
378             anyObjectDAO.save(leftEnd);
379             publisher.publishEvent(
380                     new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, leftEnd, AuthContextUtils.getDomain()));
381         });
382 
383         findUMemberships(group).forEach(membership -> {
384             User leftEnd = membership.getLeftEnd();
385             leftEnd.remove(membership);
386             membership.setRightEnd(null);
387             leftEnd.getPlainAttrs(membership).forEach(attr -> {
388                 leftEnd.remove(attr);
389                 attr.setOwner(null);
390                 attr.setMembership(null);
391 
392                 plainAttrDAO.delete(attr);
393             });
394 
395             userDAO.save(leftEnd);
396             publisher.publishEvent(
397                     new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, leftEnd, AuthContextUtils.getDomain()));
398         });
399 
400         clearUDynMembers(group);
401         clearADynMembers(group);
402 
403         entityManager().remove(group);
404     }
405 
406     @Override
407     public List<TypeExtension> findTypeExtensions(final AnyTypeClass anyTypeClass) {
408         TypedQuery<TypeExtension> query = entityManager().createQuery(
409                 "SELECT e FROM " + JPATypeExtension.class.getSimpleName()
410                 + " e WHERE :anyTypeClass MEMBER OF e.auxClasses", TypeExtension.class);
411         query.setParameter("anyTypeClass", anyTypeClass);
412 
413         return query.getResultList();
414     }
415 
416     @Transactional(readOnly = true)
417     @Override
418     public boolean existsAMembership(final String anyObjectKey, final String groupKey) {
419         Query query = entityManager().createNativeQuery(
420                 "SELECT COUNT(*) FROM " + JPAAMembership.TABLE + " WHERE group_id=? AND anyobject_it=?");
421         query.setParameter(1, groupKey);
422         query.setParameter(2, anyObjectKey);
423 
424         return ((Number) query.getSingleResult()).intValue() > 0;
425     }
426 
427     @Transactional(readOnly = true)
428     @Override
429     public boolean existsUMembership(final String userKey, final String groupKey) {
430         Query query = entityManager().createNativeQuery(
431                 "SELECT COUNT(*) FROM " + JPAUMembership.TABLE + " WHERE group_id=? AND user_id=?");
432         query.setParameter(1, groupKey);
433         query.setParameter(2, userKey);
434 
435         return ((Number) query.getSingleResult()).intValue() > 0;
436     }
437 
438     @Transactional(readOnly = true)
439     @Override
440     @SuppressWarnings("unchecked")
441     public List<String> findAMembers(final String groupKey) {
442         Query query = entityManager().createNativeQuery(
443                 "SELECT anyObject_id FROM " + JPAAMembership.TABLE + " WHERE group_id=?");
444         query.setParameter(1, groupKey);
445 
446         List<String> result = new ArrayList<>();
447         query.getResultList().stream().map(key -> key instanceof Object[]
448                 ? (String) ((Object[]) key)[0]
449                 : ((String) key)).
450                 forEach(item -> result.add((String) item));
451         return result;
452     }
453 
454     @Transactional(readOnly = true)
455     @Override
456     @SuppressWarnings("unchecked")
457     public List<String> findUMembers(final String groupKey) {
458         Query query = entityManager().createNativeQuery(
459                 "SELECT user_id FROM " + JPAUMembership.TABLE + " WHERE group_id=?");
460         query.setParameter(1, groupKey);
461 
462         List<String> result = new ArrayList<>();
463         query.getResultList().stream().map(key -> key instanceof Object[]
464                 ? (String) ((Object[]) key)[0]
465                 : ((String) key)).
466                 forEach(item -> result.add((String) item));
467         return result;
468     }
469 
470     @Override
471     @SuppressWarnings("unchecked")
472     public List<String> findADynMembers(final Group group) {
473         List<String> result = new ArrayList<>();
474 
475         group.getADynMemberships().forEach(memb -> {
476             Query query = entityManager().createNativeQuery(
477                     "SELECT any_id FROM " + ADYNMEMB_TABLE + " WHERE group_id=? AND anyType_id=?");
478             query.setParameter(1, group.getKey());
479             query.setParameter(2, memb.getAnyType().getKey());
480 
481             query.getResultList().stream().map(key -> key instanceof Object[]
482                     ? (String) ((Object[]) key)[0]
483                     : ((String) key)).
484                     filter(anyObject -> !result.contains((String) anyObject)).
485                     forEach(anyObject -> result.add((String) anyObject));
486         });
487 
488         return result;
489     }
490 
491     @Override
492     public int countAMembers(final String groupKey) {
493         Query query = entityManager().createNativeQuery(
494                 "SELECT COUNT(anyObject_id) FROM " + JPAAMembership.TABLE + " WHERE group_id=?");
495         query.setParameter(1, groupKey);
496 
497         return ((Number) query.getSingleResult()).intValue();
498     }
499 
500     @Override
501     public int countUMembers(final String groupKey) {
502         Query query = entityManager().createNativeQuery(
503                 "SELECT COUNT(user_id) FROM " + JPAUMembership.TABLE + " WHERE group_id=?");
504         query.setParameter(1, groupKey);
505 
506         return ((Number) query.getSingleResult()).intValue();
507     }
508 
509     @Override
510     public int countADynMembers(final Group group) {
511         Query query = entityManager().createNativeQuery(
512                 "SELECT COUNT(any_id) FROM " + ADYNMEMB_TABLE + " WHERE group_id=?");
513         query.setParameter(1, group.getKey());
514 
515         return ((Number) query.getSingleResult()).intValue();
516     }
517 
518     @Override
519     public int countUDynMembers(final Group group) {
520         if (group.getUDynMembership() == null) {
521             return 0;
522         }
523 
524         Query query = entityManager().createNativeQuery(
525                 "SELECT COUNT(any_id) FROM " + UDYNMEMB_TABLE + " WHERE group_id=?");
526         query.setParameter(1, group.getKey());
527 
528         return ((Number) query.getSingleResult()).intValue();
529     }
530 
531     @Override
532     public void clearADynMembers(final Group group) {
533         Query delete = entityManager().createNativeQuery("DELETE FROM " + ADYNMEMB_TABLE + " WHERE group_id=?");
534         delete.setParameter(1, group.getKey());
535         delete.executeUpdate();
536     }
537 
538     protected List<ADynGroupMembership> findWithADynMemberships(final AnyType anyType) {
539         TypedQuery<ADynGroupMembership> query = entityManager().createQuery(
540                 "SELECT e FROM " + JPAADynGroupMembership.class.getSimpleName() + " e  WHERE e.anyType=:anyType",
541                 ADynGroupMembership.class);
542         query.setParameter("anyType", anyType);
543         return query.getResultList();
544     }
545 
546     @Transactional
547     @Override
548     public Pair<Set<String>, Set<String>> refreshDynMemberships(final AnyObject anyObject) {
549         Query query = entityManager().createNativeQuery(
550                 "SELECT group_id FROM " + JPAGroupDAO.ADYNMEMB_TABLE + " WHERE any_id=?");
551         query.setParameter(1, anyObject.getKey());
552 
553         Set<String> before = new HashSet<>();
554         Set<String> after = new HashSet<>();
555         findWithADynMemberships(anyObject.getType()).forEach(memb -> {
556             boolean matches = anyMatchDAO.matches(anyObject, buildDynMembershipCond(memb.getFIQLCond()));
557             if (matches) {
558                 after.add(memb.getGroup().getKey());
559             }
560 
561             Query find = entityManager().createNativeQuery(
562                     "SELECT any_id FROM " + ADYNMEMB_TABLE + " WHERE group_id=? AND any_id=?");
563             find.setParameter(1, memb.getGroup().getKey());
564             find.setParameter(2, anyObject.getKey());
565             boolean existing = !find.getResultList().isEmpty();
566             if (existing) {
567                 before.add(memb.getGroup().getKey());
568             }
569 
570             if (matches && !existing) {
571                 Query insert = entityManager().createNativeQuery(
572                         "INSERT INTO " + ADYNMEMB_TABLE + " VALUES(?, ?, ?)");
573                 insert.setParameter(1, anyObject.getType().getKey());
574                 insert.setParameter(2, anyObject.getKey());
575                 insert.setParameter(3, memb.getGroup().getKey());
576                 insert.executeUpdate();
577             } else if (!matches && existing) {
578                 Query delete = entityManager().createNativeQuery(
579                         "DELETE FROM " + ADYNMEMB_TABLE + " WHERE group_id=? AND any_id=?");
580                 delete.setParameter(1, memb.getGroup().getKey());
581                 delete.setParameter(2, anyObject.getKey());
582                 delete.executeUpdate();
583             }
584 
585             publisher.publishEvent(new EntityLifecycleEvent<>(
586                     this, SyncDeltaType.UPDATE, memb.getGroup(), AuthContextUtils.getDomain()));
587         });
588 
589         return Pair.of(before, after);
590     }
591 
592     @Override
593     public Set<String> removeDynMemberships(final AnyObject anyObject) {
594         List<Group> dynGroups = anyObjectDAO.findDynGroups(anyObject.getKey());
595 
596         Query delete = entityManager().createNativeQuery("DELETE FROM " + ADYNMEMB_TABLE + " WHERE any_id=?");
597         delete.setParameter(1, anyObject.getKey());
598         delete.executeUpdate();
599 
600         Set<String> before = new HashSet<>();
601         dynGroups.forEach(group -> {
602             before.add(group.getKey());
603 
604             publisher.publishEvent(new EntityLifecycleEvent<>(
605                     this, SyncDeltaType.UPDATE, group, AuthContextUtils.getDomain()));
606         });
607 
608         return before;
609     }
610 
611     @Override
612     @SuppressWarnings("unchecked")
613     public List<String> findUDynMembers(final Group group) {
614         if (group.getUDynMembership() == null) {
615             return List.of();
616         }
617 
618         Query query = entityManager().createNativeQuery(
619                 "SELECT any_id FROM " + UDYNMEMB_TABLE + " WHERE group_id=?");
620         query.setParameter(1, group.getKey());
621 
622         List<String> result = new ArrayList<>();
623         query.getResultList().stream().map(key -> key instanceof Object[]
624                 ? (String) ((Object[]) key)[0]
625                 : ((String) key)).
626                 forEach(user -> result.add((String) user));
627         return result;
628     }
629 
630     @Override
631     public void clearUDynMembers(final Group group) {
632         Query delete = entityManager().createNativeQuery("DELETE FROM " + UDYNMEMB_TABLE + " WHERE group_id=?");
633         delete.setParameter(1, group.getKey());
634         delete.executeUpdate();
635     }
636 
637     protected List<UDynGroupMembership> findWithUDynMemberships() {
638         TypedQuery<UDynGroupMembership> query = entityManager().createQuery(
639                 "SELECT e FROM " + JPAUDynGroupMembership.class.getSimpleName() + " e",
640                 UDynGroupMembership.class);
641 
642         return query.getResultList();
643     }
644 
645     @Transactional
646     @Override
647     public Pair<Set<String>, Set<String>> refreshDynMemberships(final User user) {
648         Query query = entityManager().createNativeQuery(
649                 "SELECT group_id FROM " + JPAGroupDAO.UDYNMEMB_TABLE + " WHERE any_id=?");
650         query.setParameter(1, user.getKey());
651 
652         Set<String> before = new HashSet<>();
653         Set<String> after = new HashSet<>();
654         findWithUDynMemberships().forEach(memb -> {
655             boolean matches = anyMatchDAO.matches(user, buildDynMembershipCond(memb.getFIQLCond()));
656             if (matches) {
657                 after.add(memb.getGroup().getKey());
658             }
659 
660             Query find = entityManager().createNativeQuery(
661                     "SELECT any_id FROM " + UDYNMEMB_TABLE + " WHERE group_id=? AND any_id=?");
662             find.setParameter(1, memb.getGroup().getKey());
663             find.setParameter(2, user.getKey());
664             boolean existing = !find.getResultList().isEmpty();
665             if (existing) {
666                 before.add(memb.getGroup().getKey());
667             }
668 
669             if (matches && !existing) {
670                 Query insert = entityManager().createNativeQuery(
671                         "INSERT INTO " + UDYNMEMB_TABLE + " VALUES(?, ?)");
672                 insert.setParameter(1, user.getKey());
673                 insert.setParameter(2, memb.getGroup().getKey());
674                 insert.executeUpdate();
675             } else if (!matches && existing) {
676                 Query delete = entityManager().createNativeQuery(
677                         "DELETE FROM " + UDYNMEMB_TABLE + " WHERE group_id=? AND any_id=?");
678                 delete.setParameter(1, memb.getGroup().getKey());
679                 delete.setParameter(2, user.getKey());
680                 delete.executeUpdate();
681             }
682 
683             publisher.publishEvent(new EntityLifecycleEvent<>(
684                     this, SyncDeltaType.UPDATE, memb.getGroup(), AuthContextUtils.getDomain()));
685         });
686 
687         return Pair.of(before, after);
688     }
689 
690     @Override
691     public Set<String> removeDynMemberships(final User user) {
692         List<Group> dynGroups = userDAO.findDynGroups(user.getKey());
693 
694         Query delete = entityManager().createNativeQuery("DELETE FROM " + UDYNMEMB_TABLE + " WHERE any_id=?");
695         delete.setParameter(1, user.getKey());
696         delete.executeUpdate();
697 
698         Set<String> before = new HashSet<>();
699         dynGroups.forEach(group -> {
700             before.add(group.getKey());
701 
702             publisher.publishEvent(new EntityLifecycleEvent<>(
703                     this, SyncDeltaType.UPDATE, group, AuthContextUtils.getDomain()));
704         });
705 
706         return before;
707     }
708 
709     @Transactional(readOnly = true)
710     @Override
711     public Collection<String> findAllResourceKeys(final String key) {
712         return find(key).getResources().stream().map(ExternalResource::getKey).collect(Collectors.toList());
713     }
714 }