package edu.harvard.iq.dataverse.authorization.groups.impl.explicit; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; import edu.harvard.iq.dataverse.authorization.RoleAssignee; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.inject.Named; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; /** * A bean providing the {@link ExplicitGroupProvider}s with container services, * such as database connectivity. * * @author michael */ @Named @Stateless public class ExplicitGroupServiceBean { @EJB private RoleAssigneeServiceBean roleAssigneeSvc; @PersistenceContext(unitName = "VDCNet-ejbPU") protected EntityManager em; ExplicitGroupProvider provider; @PostConstruct void setup() { provider = new ExplicitGroupProvider(this, roleAssigneeSvc); } public ExplicitGroupProvider getProvider() { return provider; } public ExplicitGroup persist( ExplicitGroup g ) { if ( g.getId() == null ) { em.persist( g ); return g; } else { // clean stale data once in a while if ( Math.random() >= 0.5 ) { Set<String> stale = new TreeSet<>(); for ( String idtf : g.getContainedRoleAssignees()) { if ( roleAssigneeSvc.getRoleAssignee(idtf) == null ) { stale.add(idtf); } } if ( ! stale.isEmpty() ) { g.getContainedRoleAssignees().removeAll(stale); } } return em.merge( g ); } } public List<ExplicitGroup> findByOwner( Long dvObjectId ) { return provider.updateProvider(em.createNamedQuery( "ExplicitGroup.findByOwnerId" ) .setParameter("ownerId", dvObjectId ) .getResultList()); } ExplicitGroup findByAlias(String groupAlias) { try { return provider.updateProvider( em.createNamedQuery("ExplicitGroup.findByAlias", ExplicitGroup.class) .setParameter("alias", groupAlias) .getSingleResult()); } catch ( NoResultException nre ) { return null; } } public ExplicitGroup findInOwner(Long ownerId, String groupAliasInOwner) { try { return provider.updateProvider( em.createNamedQuery("ExplicitGroup.findByOwnerIdAndAlias", ExplicitGroup.class) .setParameter("alias", groupAliasInOwner) .setParameter("ownerId", ownerId) .getSingleResult()); } catch ( NoResultException nre ) { return null; } } public void removeGroup(ExplicitGroup explicitGroup) { em.remove( explicitGroup ); } /** * Returns all the explicit groups that are available in the context of the passed DvObject. * @param d The DvObject where the groups are queried * @return All the explicit groups defined at {@code d} and its ancestors. */ public Set<ExplicitGroup> findAvailableFor( DvObject d ) { Set<ExplicitGroup> egs = new HashSet<>(); while ( d != null ) { egs.addAll( findByOwner(d.getId()) ); d = d.getOwner(); } return provider.updateProvider( egs ); } /** * Finds all the explicit groups {@code ra} is a member of. * @param ra the role assignee whose membership list we seek * @return set of the explicit groups that contain {@code ra}. */ public Set<ExplicitGroup> findGroups( RoleAssignee ra ) { return findClosure(findDirectlyContainingGroups(ra)); } /** * Finds all the explicit groups {@code ra} is <b>directly</b> a member of. * To find all these groups and the groups the contain them (recursively upwards), * consider using {@link #findGroups(edu.harvard.iq.dataverse.authorization.RoleAssignee)} * @param ra the role assignee whose membership list we seek * @return set of the explicit groups that contain {@code ra} directly. * @see #findGroups(edu.harvard.iq.dataverse.authorization.RoleAssignee) */ public Set<ExplicitGroup> findDirectlyContainingGroups( RoleAssignee ra ) { if ( ra instanceof AuthenticatedUser ) { return provider.updateProvider( new HashSet<>( em.createNamedQuery("ExplicitGroup.findByAuthenticatedUserIdentifier", ExplicitGroup.class) .setParameter("authenticatedUserIdentifier", ra.getIdentifier().substring(1)) .getResultList() )); } else if ( ra instanceof ExplicitGroup ) { return provider.updateProvider( new HashSet<>( em.createNamedQuery("ExplicitGroup.findByContainedExplicitGroupId", ExplicitGroup.class) .setParameter("containedExplicitGroupId", ((ExplicitGroup) ra).getId()) .getResultList() )); } else { return provider.updateProvider( new HashSet<>( em.createNamedQuery("ExplicitGroup.findByRoleAssgineeIdentifier", ExplicitGroup.class) .setParameter("roleAssigneeIdentifier", ra.getIdentifier()) .getResultList() )); } } /** * Finds all the groups {@code ra} is a member of, in the context of {@code o}. * This includes both direct and indirect memberships. * @param ra The role assignee whose memberships we seek. * @param o The {@link DvObject} whose context we search. * @return All the groups in {@code o}'s context that {@code ra} is a member of. */ public Set<ExplicitGroup> findGroups( RoleAssignee ra, DvObject o ) { Set<ExplicitGroup> directGroups = findDirectGroups(ra, o); Set<ExplicitGroup> closure = findClosure(directGroups); return closure.stream() .filter( g -> g.owner.isAncestorOf(o) ) .collect( Collectors.toSet() ); } /** * Finds all the groups {@code ra} directly belongs to in the context of {@code o}. In effect, * collects all the groups {@code ra} belongs to and that are defined at {@code o} * or one of its ancestors. * * <B>Does not take group containment into account.</B> Use * * @param ra The role assignee that belongs to the groups * @param o the DvObject that defines the context of the search. * @return All the groups ra belongs to in the context of o. */ public Set<ExplicitGroup> findDirectGroups( RoleAssignee ra, DvObject o ) { if ( o == null ) return Collections.emptySet(); List<ExplicitGroup> groupList = new LinkedList<>(); if ( ra instanceof ExplicitGroup ) { for ( DvObject cur = o; cur != null; cur=cur.getOwner() ) { groupList.addAll( em.createNamedQuery("ExplicitGroup.findByOwnerAndSubExGroupId", ExplicitGroup.class) .setParameter("ownerId", cur.getId()) .setParameter("subExGroupId", ((ExplicitGroup)ra).getId()) .getResultList() ); } } else if ( ra instanceof AuthenticatedUser ) { for ( DvObject cur = o; cur != null; cur=cur.getOwner() ) { groupList.addAll( em.createNamedQuery("ExplicitGroup.findByOwnerAndAuthUserId", ExplicitGroup.class) .setParameter("ownerId", cur.getId()) .setParameter("authUserId", ((AuthenticatedUser)ra).getId()) .getResultList() ); } } else { for ( DvObject cur = o; cur != null; cur=cur.getOwner() ) { groupList.addAll( em.createNamedQuery("ExplicitGroup.findByOwnerAndRAIdtf", ExplicitGroup.class) .setParameter("ownerId", cur.getId()) .setParameter("raIdtf", ra.getIdentifier()) .getResultList() ); } } return provider.updateProvider( new HashSet<>(groupList) ); } /** * * Finds all the groups that contain the groups in {@code seed} (including {@code seed}), and the * groups that contain these groups, an so on. * * @param seed the initial set of groups. * @return Transitive closure (based on group containment) of the groups in {@code seed}. */ protected Set<ExplicitGroup> findClosure( Set<ExplicitGroup> seed ) { Set result = new HashSet<>(); // The set of groups whose parents were not visited yet. Set<ExplicitGroup> fringe = new HashSet<>(seed); while ( ! fringe.isEmpty() ) { ExplicitGroup g = fringe.iterator().next(); fringe.remove(g); result.add(g); // add all of g's parents to the fringe, unless already visited. findDirectlyContainingGroups(g).stream() .filter( eg -> !(result.contains(eg)||fringe.contains(eg) )) .forEach( fringe::add ); } return result; } }