/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.security.provider;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.groups.IEntityGroup;
import org.apereo.portal.groups.IGroupMember;
import org.apereo.portal.permission.IPermissionActivity;
import org.apereo.portal.permission.IPermissionOwner;
import org.apereo.portal.permission.dao.IPermissionOwnerDao;
import org.apereo.portal.permission.target.IPermissionTarget;
import org.apereo.portal.permission.target.IPermissionTargetProvider;
import org.apereo.portal.permission.target.IPermissionTargetProviderRegistry;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.registry.IPortletDefinitionRegistry;
import org.apereo.portal.security.IAuthorizationPrincipal;
import org.apereo.portal.security.IAuthorizationService;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.PermissionHelper;
import org.apereo.portal.services.GroupService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
/**
* Responsible for pre-loading the permissions cache maintained by the
* anyUnblockedGrantPermissionPolicy bean with some portlet-related evaluation decisions. This is
* computationally expensive work, especially for operations that need a decision for every portlet
* in the registry; it's better to do it outside of a request thread.
*
* @since 4.3
*/
public class PortletPermissionsCachePrimer {
@Autowired private IPortletDefinitionRegistry portletDefinitionRegistry;
@Autowired private IPermissionOwnerDao permissionOwnerDao;
@Autowired private IPermissionTargetProviderRegistry targetProviderRegistry;
@Autowired private IAuthorizationService authorizationService;
@Autowired private AnyUnblockedGrantPermissionPolicy policy;
private final Logger log = LoggerFactory.getLogger(getClass());
private ThreadPoolExecutor executor;
private Map<String, Set<String>> permissionsMap;
@Required
public void setExecutor(ThreadPoolExecutor executor) {
this.executor = executor;
}
@Required
public void setPermissionsMap(Map<String, Set<String>> permissionsMap) {
this.permissionsMap = Collections.unmodifiableMap(permissionsMap);
}
public void primeCache() {
if (executor.getActiveCount() != 0) {
log.warn(
"Skipping this run becasue there are active threads in the executor, signifying the previous run is not complete");
return;
}
log.info("STARTING PortletPermissionsCachePrimer.primeCache()...");
final long timestamp = System.currentTimeMillis();
/*
* This task is pretty effort-intensive and may take in excess of a
* minute to run in a single thread. Going to use a divide-and-conquer
* approach.
*/
final Map<NodeWalker, Future<NodeWalkerReport>> futures = new HashMap<>();
final IEntityGroup rootGroup = GroupService.getRootGroup(IPerson.class);
for (Map.Entry<String, Set<String>> y : permissionsMap.entrySet()) {
final IPermissionOwner owner = permissionOwnerDao.getPermissionOwner(y.getKey());
for (String s : y.getValue()) {
final IPermissionActivity activity =
permissionOwnerDao.getPermissionActivity(y.getKey(), s);
final IPermissionTargetProvider targetProvider =
targetProviderRegistry.getTargetProvider(activity.getTargetProviderKey());
final NodeWalker walker =
new NodeWalker(rootGroup, owner, activity, targetProvider);
final Future<NodeWalkerReport> future = this.executor.submit(walker);
futures.put(walker, future);
}
}
int totalCombinations = 0;
for (Map.Entry<NodeWalker, Future<NodeWalkerReport>> y : futures.entrySet()) {
try {
final NodeWalkerReport report = y.getValue().get();
totalCombinations += report.getCombinationCount();
log.debug(
"NodeWalker '{}' processed {} combinations in {}ms",
y.getKey(),
report.getCombinationCount(),
report.getDuration());
} catch (InterruptedException | ExecutionException e) {
log.error("NodeWalker '{}' failed", y.getKey());
}
}
log.info(
"COMPLETED PortletPermissionsCachePrimer.primeCache(); processed {} total combinations in {}ms",
totalCombinations,
Long.toString(System.currentTimeMillis() - timestamp));
}
/*
* Nested Types
*/
private /* non-static */ final class NodeWalker implements Callable<NodeWalkerReport> {
final IEntityGroup rootGroup;
final IPermissionOwner owner;
final IPermissionActivity activity;
final IPermissionTargetProvider targetProvider;
public NodeWalker(
IEntityGroup rootGroup,
IPermissionOwner owner,
IPermissionActivity activity,
IPermissionTargetProvider targetProvider) {
this.rootGroup = rootGroup;
this.owner = owner;
this.activity = activity;
this.targetProvider = targetProvider;
}
@Override
public NodeWalkerReport call() throws Exception {
final NodeWalkerReport rslt = new NodeWalkerReport();
final long timestamp = System.currentTimeMillis();
walk(rootGroup, new HashSet<EntityIdentifier>(), rslt);
rslt.setDuration(System.currentTimeMillis() - timestamp);
return rslt;
}
private void walk(
final IEntityGroup group,
final Set<EntityIdentifier> visitedNodes,
final NodeWalkerReport report) {
/*
* Recursive groups structures are a bad idea, but
* we will attempt to prevent issues with them.
*/
if (visitedNodes.contains(group.getUnderlyingEntityIdentifier())) {
return;
}
visitedNodes.add(group.getUnderlyingEntityIdentifier());
/*
* First we load ourselves.
*/
final IAuthorizationPrincipal principal = authorizationService.newPrincipal(group);
final List<IPortletDefinition> portletDefinitions =
portletDefinitionRegistry.getAllPortletDefinitions();
for (IPortletDefinition pdef : portletDefinitions) {
final String targetString =
PermissionHelper.permissionTargetIdForPortletDefinition(pdef);
final IPermissionTarget target = targetProvider.getTarget(targetString);
policy.loadInCache(authorizationService, principal, owner, activity, target);
report.incrementCombinationCount();
}
/*
* Then we load our children.
*/
if (group.hasMembers()) {
final Set<IGroupMember> members = group.getChildren();
for (IGroupMember member : members) {
if (member.isGroup()) {
IEntityGroup child = (IEntityGroup) member;
walk(child, visitedNodes, report);
}
}
}
}
@Override
public String toString() {
return "NodeWalker [rootGroup="
+ rootGroup
+ ", owner="
+ owner
+ ", activity="
+ activity
+ "]";
}
}
private static final class NodeWalkerReport {
private int combinationCount;
private long duration;
public int getCombinationCount() {
return combinationCount;
}
public void incrementCombinationCount() {
combinationCount += 1;
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
}
}