All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.edu.icm.unity.engine.bulk.BulkQueryServiceImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2018 Bixbit - Krzysztof Benedyczak. All rights reserved.
 * See LICENCE.txt file for licensing information.
 */
package pl.edu.icm.unity.engine.bulk;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;

import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import com.google.common.base.Stopwatch;

import pl.edu.icm.unity.base.attribute.AttributeExt;
import pl.edu.icm.unity.base.authn.CredentialInfo;
import pl.edu.icm.unity.base.entity.Entity;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.exceptions.InternalException;
import pl.edu.icm.unity.base.group.Group;
import pl.edu.icm.unity.base.group.GroupContents;
import pl.edu.icm.unity.base.identity.Identity;
import pl.edu.icm.unity.base.registration.EnquiryForm;
import pl.edu.icm.unity.base.tx.Transactional;
import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.authn.IllegalCredentialException;
import pl.edu.icm.unity.engine.api.authn.local.LocalCredentialsRegistry;
import pl.edu.icm.unity.engine.api.bulk.BulkGroupQueryService;
import pl.edu.icm.unity.engine.api.bulk.EntityGroupAttributes;
import pl.edu.icm.unity.engine.api.bulk.EntityInGroupData;
import pl.edu.icm.unity.engine.api.bulk.GroupMembershipData;
import pl.edu.icm.unity.engine.api.bulk.GroupStructuralData;
import pl.edu.icm.unity.engine.api.bulk.GroupsWithMembers;
import pl.edu.icm.unity.engine.api.exceptions.RuntimeEngineException;
import pl.edu.icm.unity.engine.attribute.AttributeStatementProcessor;
import pl.edu.icm.unity.engine.authz.AuthzCapability;
import pl.edu.icm.unity.engine.authz.InternalAuthorizationManager;
import pl.edu.icm.unity.engine.credential.CredentialRequirementsHolder;
import pl.edu.icm.unity.engine.credential.EntityCredentialsHelper;
import pl.edu.icm.unity.engine.forms.enquiry.EnquiryTargetCondEvaluator;
import pl.edu.icm.unity.store.api.tx.TransactionalRunner;

@Component
@Primary
class BulkQueryServiceImpl implements BulkGroupQueryService
{
	private static final Logger log = Log.getLogger(Log.U_SERVER_BULK_OPS, BulkQueryServiceImpl.class);
	
	private final AttributeStatementProcessor statementsHelper;
	private final EntityCredentialsHelper credentialsHelper;
	private final LocalCredentialsRegistry localCredReg;
	private final CompositeEntitiesInfoProvider dataProvider;
	private final InternalAuthorizationManager authz;
	private final TransactionalRunner tx;
	private final ForkJoinPool pool = ForkJoinPool.commonPool();
	
	@Autowired
	public BulkQueryServiceImpl(AttributeStatementProcessor statementsHelper,
			EntityCredentialsHelper credentialsHelper,
			LocalCredentialsRegistry localCredReg,
			CompositeEntitiesInfoProvider dataProvider,
			InternalAuthorizationManager authz,
			TransactionalRunner tx)
	{
		this.statementsHelper = statementsHelper;
		this.credentialsHelper = credentialsHelper;
		this.localCredReg = localCredReg;
		this.dataProvider = dataProvider;
		this.authz = authz;
		this.tx = tx;
	}


	@Override
	public GroupsWithMembers getMembersWithAttributeForAllGroups(String rootGroup, Set groupFilter)
	{
		Stopwatch watch = Stopwatch.createStarted();
		if (groupFilter.stream().filter(grp -> !Group.isChildOrSame(grp, rootGroup)).findAny().isPresent())
			throw new IllegalArgumentException("All filter groups must be child of the rootGroup");
		MultiGroupMembershipData data = getMultiGroupMembershipData(rootGroup, groupFilter);
		GroupsWithMembers ret = assembleGroupsWithAttributes(data);
		log.debug("Bulk multi-group membership data retrieval of {} groups: {}", 
				ret.membersByGroup.keySet().size(), watch.toString());
		return ret; 
	}

	private MultiGroupMembershipData getMultiGroupMembershipData(String rootGroup, Set groupFilter)
	{
		try
		{
			return tx.runInTransactionRetThrowing(() -> 
			{
				authz.checkAuthorization(AuthzCapability.readHidden, AuthzCapability.read);
				return dataProvider.getCompositeMultiGroupContents(rootGroup, groupFilter);
			});
		} catch (EngineException e)
		{
			throw new RuntimeEngineException(e);
		}
	}

	private GroupsWithMembers assembleGroupsWithAttributes(MultiGroupMembershipData data)
	{
		Map entities = getGroupEntitiesNoContext(false, data.entitiesData, data.globalSystemData);
		
		List tasks = new ArrayList<>(data.groups.size());
		for (String group: data.groups)
			tasks.add(new TaskWithGroup(
					pool.submit(() -> getGroupEntityAttribtues(group, data)), 
					group));

		Map> attributes = new HashMap<>(); 
		for (TaskWithGroup task: tasks)
			try
			{
				attributes.put(task.group, task.task.get());
			} catch (InterruptedException e)
			{
				Thread.currentThread().interrupt();
				throw new RuntimeException("Interrupted while waiting for tasks", e);
			} catch (ExecutionException e)
			{
				throw new RuntimeException("Error in concurrent task", e);
			}
		return new GroupsWithMembers(entities, attributes);
	}
	
	private static class TaskWithGroup
	{
		private final ForkJoinTask> task;
		private final String group;
		
		TaskWithGroup(ForkJoinTask> task, String group)
		{
			this.task = task;
			this.group = group;
		}
	}
	
	private List getGroupEntityAttribtues(String group, MultiGroupMembershipData data)
	{
		Map> groupUsersAttributes = 
				getGroupUsersAttributes(group, data.entitiesData, data.globalSystemData);
		List groupEntityAttributes = new ArrayList<>(groupUsersAttributes.size());
		for (Map.Entry> entry: groupUsersAttributes.entrySet())
			groupEntityAttributes.add(new EntityGroupAttributes(entry.getKey(), entry.getValue()));
		return groupEntityAttributes;
	}
	
	@Transactional
	@Override
	public GroupMembershipData getBulkMembershipData(String group, Set filter) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.readHidden, AuthzCapability.read);
		return dataProvider.getCompositeGroupContents(group, Optional.ofNullable(filter));
	}
	
	@Transactional
	@Override
	public GroupMembershipData getBulkMembershipData(String group) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.readHidden, AuthzCapability.read);
		return dataProvider.getCompositeGroupContents(group, Optional.empty());
	}

	@Transactional
	@Override
	public GroupStructuralData getBulkStructuralData(String group) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.readHidden, AuthzCapability.read);
		return dataProvider.getGroupStructuralContents(group);
	}
	
	/**
	 * @return all effective attributes of all entities in the group (including disabled ones)
	 */
	@Override
	public Map> getGroupUsersAttributes(String group, GroupMembershipData dataO)
	{
		GroupMembershipDataImpl data = (GroupMembershipDataImpl) dataO;
		return getGroupUsersAttributes(group, data.entitiesData, data.globalSystemData);
	}
	
	private Map> getGroupUsersAttributes(String group, EntitiesData entitiesData, 
			GlobalSystemData globalSystemData)
	{
		Stopwatch watch = Stopwatch.createStarted();
		Map> ret = new HashMap<>();
		for (Long entityId: entitiesData.getEntityInfo().keySet())
		{
			Set memberships = entitiesData.getMemberships().get(entityId);
			if (memberships != null && memberships.contains(group))
				ret.put(entityId, getAllAttributesAsMap(entityId, group, entitiesData, globalSystemData));
		}
		log.debug("Bulk attributes assembly of {}: {}", group, watch.toString());
		return ret;
	}
	
	@Override
	public Map getMembershipInfo(GroupMembershipData dataO)
	{
		Stopwatch watch = Stopwatch.createStarted();
		GroupMembershipDataImpl data = (GroupMembershipDataImpl) dataO;
		Map> memberships = data.entitiesData.getMemberships();
		Map ret = new HashMap<>();

		for (Long e : memberships.keySet())
		{
			CredentialInfo credentialInfo = getCredentialInfo(e, data.entitiesData, data.globalSystemData);
			Entity entity = assembleEntity(e, false, data.entitiesData, data.globalSystemData);
			Map groupAttributesAsMap = getAllAttributesAsMap(e, data.group, 
					data.entitiesData, data.globalSystemData);
			Map rootAttributesAsMap = data.group.equals("/") ? 
					groupAttributesAsMap : 
					getAllAttributesAsMap(e, "/", data.entitiesData, data.globalSystemData);
			ret.put(e, new EntityInGroupData(
					entity,
					data.group,
					memberships.get(e), 
					groupAttributesAsMap,
					rootAttributesAsMap,
					getEnquiryForms(e, data, credentialInfo)));
		}

		log.debug("Bulk members with groups: {}", watch.toString());
		return ret;
	}

	private Set getEnquiryForms(Long e, GroupMembershipDataImpl data, CredentialInfo credentialInfo)
	{
		Set forms = new HashSet<>();

		for (EnquiryForm enqForm : data.globalSystemData.getEnquiryForms().values())
		{
			if (EnquiryTargetCondEvaluator.evaluateTargetCondition(enqForm,
							data.entitiesData.getIdentities().get(e),
							data.entitiesData.getEntityInfo().get(e).getEntityState().toString(),
							credentialInfo,
							data.entitiesData.getMemberships().get(e),
							data.entitiesData.getDirectAttributes().get(e).get("/").values()))
				forms.add(enqForm.getName());
		}
		return forms;
	}
	
	@Override
	public Map getGroupEntitiesNoContextWithTargeted(GroupMembershipData dataO)
	{
		GroupMembershipDataImpl data = (GroupMembershipDataImpl) dataO;
		return getGroupEntitiesNoContext(true, data.entitiesData, data.globalSystemData);
	}

	@Override
	public Map getGroupEntitiesNoContextWithoutTargeted(GroupMembershipData dataO)
	{
		GroupMembershipDataImpl data = (GroupMembershipDataImpl) dataO;
		return getGroupEntitiesNoContext(false, data.entitiesData, data.globalSystemData);
	}
	

	@Override
	public Map getGroupAndSubgroups(GroupStructuralData dataO)
	{
		Stopwatch watch = Stopwatch.createStarted();
		Map ret = new HashMap<>();
		GroupStructuralDataImpl data = (GroupStructuralDataImpl) dataO;
		GroupsTree groupsTree = new GroupsTree(data.getGroups().values());
		for (Group group: data.getGroups().values())
		{
			if (!Group.isChildOrSame(group.toString(), data.getGroup()))
				continue;
			GroupContents entry = new GroupContents();
			entry.setGroup(group);
			entry.setSubGroups(groupsTree.getDirectSubGroups(group.toString()));
			ret.put(group.toString(), entry);
		}
		log.debug("Bulk group and subgroups resolve: {}", watch.toString());
		return ret;
	}
	
	@Override
	public Map getGroupAndSubgroups(GroupStructuralData dataO, String subGroup)
	{
		GroupStructuralDataImpl data = (GroupStructuralDataImpl) dataO;
		if (!Group.isChildOrSame(subGroup, data.getGroup()))
		{
			throw new IllegalArgumentException(
					"Group " + subGroup + " is not child of group structural data root group " + data.getGroup());
		}
		Stopwatch watch = Stopwatch.createStarted();
		Map ret = new HashMap<>();

		GroupsTree groupsTree = new GroupsTree(data.getGroups().values());
		for (Group group : data.getGroups().values())
		{
			if (!Group.isChildOrSame(group.toString(), subGroup))
				continue;
			GroupContents entry = new GroupContents();
			entry.setGroup(group);
			entry.setSubGroups(groupsTree.getDirectSubGroups(group.toString()));
			ret.put(group.toString(), entry);
		}
		log.debug("Bulk group and subgroups resolve: {}", watch.toString());
		return ret;
	}
	
	private Map getGroupEntitiesNoContext(boolean includeTargeted, 
			EntitiesData entitiesData, GlobalSystemData globalSystemData)
	{
		Stopwatch watch = Stopwatch.createStarted();
		Map ret = new HashMap<>();
		for (Long entityId: entitiesData.getEntityInfo().keySet())
			ret.put(entityId, assembleEntity(entityId, includeTargeted, entitiesData, globalSystemData));
		log.debug("Bulk entities assembly: {}", watch.toString());
		return ret;
	}
	
	private Entity assembleEntity(long entityId, boolean includeTargeted, EntitiesData entitiesData, 
			GlobalSystemData globalSystemData)
	{
		CredentialInfo credInfo = getCredentialInfo(entityId, entitiesData, globalSystemData);
		List identitites = entitiesData.getIdentities().get(entityId);
		if (!includeTargeted)
			identitites = filterTargetedIdentitites(identitites);
		return new Entity(identitites, entitiesData.getEntityInfo().get(entityId), credInfo);
	}
	
	private List filterTargetedIdentitites(List all)
	{
		return all.stream().filter(id -> id.getTarget() == null).collect(Collectors.toList());
	}
	
	private Map getAllAttributesAsMap(long entityId, String group, 
			EntitiesData entitiesData, GlobalSystemData globalSystemData) 
	{
		Map> directAttributesByGroup = entitiesData.getDirectAttributes().get(entityId);
		List allGroups = entitiesData.getMemberships().get(entityId)
				.stream()
				.map(g -> globalSystemData.getGroups().get(g))
				.collect(Collectors.toList());
		List identities = entitiesData.getIdentities().get(entityId);
		return statementsHelper.getEffectiveAttributes(identities, 
				group, null, allGroups, directAttributesByGroup, 
				globalSystemData.getAttributeClasses(),
				g -> globalSystemData.getGroups().get(g),
				globalSystemData.getAttributeTypes()::get,
				g -> globalSystemData.getCachingMVELGroupProvider().get(g));
	}
	
	private CredentialInfo getCredentialInfo(long entityId, EntitiesData entitiesData, GlobalSystemData globalSystemData)
	{
		Map attributes = entitiesData.getDirectAttributes().get(entityId).get("/");
		String credentialRequirementId = credentialsHelper.getCredentialReqFromAttribute(attributes);
		
		CredentialRequirementsHolder credReq;
		try
		{
			credReq = new CredentialRequirementsHolder(localCredReg, 
					globalSystemData.getCredentialRequirements().get(credentialRequirementId), 
					globalSystemData.getCredentials());
		} catch (IllegalCredentialException e)
		{
			throw new InternalException("Unknown credential assigned to entity", e);
		}
		return credentialsHelper.getCredentialInfoNoQuery(entityId, attributes, credReq, credentialRequirementId);
	}
	
	static class GroupNode
	{
		private final String path;
		private final List children;

		GroupNode(String path)
		{
			this.path = path;
			this.children = new ArrayList<>();
		}

		void addChild(String childPath)
		{
			children.add(childPath);
		}

		List getDirectChildrenPaths()
		{
			return Collections.unmodifiableList(children);
		}

		String getPath()
		{
			return path;
		}
	}

	static class GroupsTree
	{
		private final GroupNode root;
		private final Map pathToGroupNode;

		GroupsTree(Collection groups)
		{
			this.root = new GroupNode("/");
			this.pathToGroupNode = new HashMap<>();
			this.pathToGroupNode.put("/", root);
			buildTree(groups);
		}

		private void buildTree(Collection groups)
		{
			groups.forEach(this::addGroup);
		}

		private void addGroup(Group group)
		{
			if (pathToGroupNode.containsKey(group.getPathEncoded()))
			{
				return;
			}
			
			GroupNode current = root;
			StringBuilder processedPathBuilder = new StringBuilder();
			for (String part : group.getPath())
			{
				if (part.isEmpty())
				{
					continue;
				}
				processedPathBuilder.append("/").append(part);
				String processedPath = processedPathBuilder.toString();
				
				if (!pathToGroupNode.containsKey(processedPath))
				{
					GroupNode newNode = new GroupNode(processedPath);
					current.addChild(processedPath);
					pathToGroupNode.put(processedPath, newNode);
				}
				current = pathToGroupNode.get(processedPath);
			}
		}

		List getDirectSubGroups(String path)
		{
			GroupNode node = pathToGroupNode.get(path);
			return node == null ? List.of() : node.getDirectChildrenPaths();
		}

	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy