/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package edu.harvard.iq.dataverse.mydata;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.DvObjectServiceBean;
import edu.harvard.iq.dataverse.RoleAssigneeServiceBean;
import edu.harvard.iq.dataverse.search.SolrQueryResponse;
import edu.harvard.iq.dataverse.search.SolrSearchResult;
import edu.harvard.iq.dataverse.authorization.DataverseRolePermissionHelper;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.search.SearchConstants;
import java.util.ArrayList;
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.logging.Level;
import java.util.logging.Logger;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import org.apache.commons.lang.StringUtils;
/**
* Input: dvObject id, parent Id, and dvObject type (from Solr)
* Output: For each dvObject id, a list of Role names
*
* @author rmp553
*/
public class RoleTagRetriever {
private static final Logger logger = Logger.getLogger(RoleTagRetriever.class.getCanonicalName());
private final DataverseRolePermissionHelper rolePermissionHelper;
private final RoleAssigneeServiceBean roleAssigneeService;
private final DvObjectServiceBean dvObjectServiceBean;
public boolean errorFound = false;
public String errorMessage = null;
//public Map<Long, String> roleNameLookup = new HashMap<>(); // { role id : role name }
private Map<Long, List<Long>> idToRoleListHash; // { dvobject id : [role id, role id] }
private Map<Long, Long> childToParentIdHash; // { dvobject id : parent id }
private Map<Long, String> idToDvObjectType; // { dvobject id : dvobject type }
private List<Long> datasetIdsNeedingParentIds;
private List<Long> finalCardIds;
private Map<Long, List<String>> finalIdToRolesHash; // { dvobject id : [role name, role name] }
// ----------------------------------
// Constructor
// ----------------------------------
public RoleTagRetriever(DataverseRolePermissionHelper rolePermissionHelper
, RoleAssigneeServiceBean roleAssigneeService
, DvObjectServiceBean dvObjectServiceBean){
this.rolePermissionHelper = rolePermissionHelper;
this.roleAssigneeService = roleAssigneeService;
this.dvObjectServiceBean = dvObjectServiceBean;
}
public void loadRoles(DataverseRequest dataverseRequest , SolrQueryResponse solrQueryResponse){
if (dataverseRequest == null){
throw new NullPointerException("RoleTagRetriever.constructor. dataverseRequest cannot be null");
}
AuthenticatedUser au = dataverseRequest.getAuthenticatedUser();
if (au == null){
throw new NullPointerException("RoleTagRetriever.constructor. au cannot be null");
}
String userIdentifier = au.getUserIdentifier();
if (userIdentifier == null){
throw new NullPointerException("RoleTagRetriever.constructor. userIdentifier cannot be null");
}
if (solrQueryResponse == null){
throw new NullPointerException("RoleTagRetriever.constructor. solrQueryResponse cannot be null");
}
// (1) Reset variables
initLookups();
// (2) Load roles from solr docs
loadInfoFromSolrResponseDocs(solrQueryResponse);
// (3) Load grandparent ids, if needed
findDataverseIdsForFiles();
// (4) Retrieve the role ids
retrieveRoleIdsForDvObjects(dataverseRequest, au);
// (5) Prepare final role lists
prepareFinalRoleLists();
//showRoleListHash();
}
private void initLookups(){
this.errorFound = false;
this.errorMessage = null;
this.idToRoleListHash = new HashMap(); // { dvobject id : [role id, role id] }
this.childToParentIdHash = new HashMap(); // { dvobject id : parent id }
this.idToDvObjectType = new HashMap(); // { dvobject id : dvobject type }
this.finalIdToRolesHash = new HashMap();
this.datasetIdsNeedingParentIds = new ArrayList<>();
this.finalCardIds = new ArrayList<>();
}
private void addIdNeedingRoleRetrieval(Long dvObjectId){
if (dvObjectId == null){
return;
}
// initialize with dvObject id and empty list of role ids
//
if (!this.idToRoleListHash.containsKey(dvObjectId)){
this.idToRoleListHash.put(dvObjectId, new ArrayList<Long>());
}
}
public void showRoleListHash(){
msgt("showRoleListHash");
for (Map.Entry<Long, List<Long>> entry : idToRoleListHash.entrySet()) {
msg("id: " + entry.getKey() + " | values: " + entry.getValue().toString());
}
msgt("show idToDvObjectType");
for (Map.Entry<Long, String> entry : idToDvObjectType.entrySet()) {
msg("dv id: " + entry.getKey() + " | type: " + entry.getValue());
}
for (Map.Entry<Long, List<String>> entry : finalIdToRolesHash.entrySet()) {
msg("id: " + entry.getKey() + " | values: " + entry.getValue().toString());
}
}
private void addRoleIdForHash(Long dvObjectId, Long roleId){
if ((dvObjectId == null)||(roleId == null)){
return;
}
if (!this.idToRoleListHash.containsKey(dvObjectId)){
logger.warning("DvObject id not found in hash (shouldn't happen): " + dvObjectId);
return;
}
List<Long> roldIdList = this.idToRoleListHash.get(dvObjectId);
roldIdList.add(roleId);
this.idToRoleListHash.put(dvObjectId, roldIdList);
}
/**
* Iterate through the Solr Cards and collect
* - DvObject Id + Parent ID
* - Dtype for object and parent
* - Whether a "grandparent id" is needed for a file object
*
* @param solrQueryResponse
*/
private void loadInfoFromSolrResponseDocs(SolrQueryResponse solrQueryResponse){
if (solrQueryResponse == null){
throw new NullPointerException("RoleTagRetriever.constructor. solrQueryResponse cannot be null");
}
// ----------------------------------
// Load initial data
// ----------------------------------
msgt("load initial data");
// Iterate through Solr cards
//
for (SolrSearchResult doc : solrQueryResponse.getSolrSearchResults()){
// -------------------------------------------------
// (a) retrieve Card Id and DvObject type
// -------------------------------------------------
finalCardIds.add(doc.getEntityId());
String dtype = doc.getType();
Long entityId = doc.getEntityId();
if (dtype == null){
throw new NullPointerException("The dvobject type cannot be null for SolrSearchResult");
}
logger.fine("\nid: " + doc.getEntityId() + " dtype: " + dtype);
// -------------------------------------------------
// (b) Populate dict of { dvObject id : dtype }
// e.g. { 3 : 'Dataverse' }
// -------------------------------------------------
this.idToDvObjectType.put(entityId, dtype);
// -------------------------------------------------
// (c) initialize dict of { dvObject id : [ (empty list for role ids) ] }
// -------------------------------------------------
addIdNeedingRoleRetrieval(entityId);
Long parentId = doc.getParentIdAsLong();
// -------------------------------------------------
// For datasets and files, check parents
// -------------------------------------------------
if (!(dtype.equals(SearchConstants.SOLR_DATAVERSES))){
// -------------------------------------------------
// (d) Add to the childToParentIdHash { child id : parent id }
// -------------------------------------------------
if (parentId == null){
throw new NullPointerException("A dataset or file parent cannot be null for SolrSearchResult");
}
logger.fine("\nparentId: " + parentId);
this.childToParentIdHash.put(doc.getEntityId(), parentId);
// -------------------------------------------------
// (e) For the parent, add to dict of
// { dvObject id : [ (empty list for role ids) ] }
// - similar to (c) above
// -------------------------------------------------
addIdNeedingRoleRetrieval(parentId);
// -------------------------------------------------
// (f) Add the parent to the DvObject type lookup { dvObject id : dtype }
// - similar to (b) above
// -------------------------------------------------
if (doc.getType().equals(SearchConstants.SOLR_FILES)){
logger.fine("It's a file");
// -------------------------------------------------
// (f1) This is a file, we know the parent is a Dataset
// -------------------------------------------------
this.idToDvObjectType.put(parentId, SearchConstants.SOLR_DATASETS);
// -------------------------------------------------
// (g) For files, we'll need to get roles from the grandparent--e.g., the dataverse
// -------------------------------------------------
this.datasetIdsNeedingParentIds.add(parentId);
}if (dtype.equals(SearchConstants.SOLR_DATASETS)){
logger.fine("It's a dataset");
// -------------------------------------------------
// (f2) This is a Dataset, we know the parent is a Dataverse
// -------------------------------------------------
this.idToDvObjectType.put(parentId, SearchConstants.SOLR_DATAVERSES);
}
}
// -------------------------------------------------
// initialize final hash of dvObject id and empty list of role names
// { dvObject id : [ (empty list for role nams) ] }
// -------------------------------------------------
this.finalIdToRolesHash.put(doc.getEntityId(), new ArrayList<String>());
}
}
/**
* From the Cards, we know the Parent Ids of all the DvObjects
*
* However, for files, the roles may trickle down from the Dataverses
*
* Dataverse (file downloader) -> Dataset (file downloader) -> File (file downloader)
*
* Grandparent -> Parent -> Child
*
* Therefore, we need the File's "grandparent id" -- the Dataverse ID
*
* File (from card) -> Parent (from card) -> Grandparent (NEED TO FIND)
*
*
*/
private void findDataverseIdsForFiles(){
msgt("findDataverseIdsForFiles: " + datasetIdsNeedingParentIds.toString());
// -------------------------------------
// (1) Do we have any dataset Ids where we need to find the parent dataverse?
// -------------------------------------
if (this.datasetIdsNeedingParentIds == null){
throw new NullPointerException("findDataverseIdsForFiles should not be null");
}
if (this.datasetIdsNeedingParentIds.isEmpty()){
logger.fine("No ids found!");
return;
}
// -------------------------------------
// (2) Do we have any dataset Ids where we need to find the parent dataverse?
// -------------------------------------
List<Object[]> results = this.dvObjectServiceBean.getDvObjectInfoForMyData(this.datasetIdsNeedingParentIds);
logger.fine("findDataverseIdsForFiles results count: " + results.size());
// -------------------------------------
// (2a) Nope, return
// -------------------------------------
if (results.isEmpty()){
return;
}
// -------------------------------------
// (3) Process the results -- the parent ID is the Dataverse that we're interested in
// -------------------------------------
Integer dvIdAsInteger;
Long dvId;
String dtype;
Long parentId;
// -------------------------------------
// Iterate through object list
// -------------------------------------
for (Object[] ra : results) {
dvIdAsInteger = (Integer)ra[0]; // ?? Why, should be a Long
dvId = new Long(dvIdAsInteger);
dtype = (String)ra[1];
parentId = (Long)ra[2];
//msg("result: dvId: " + dvId + " |dtype: " + dtype + " |parentId: " + parentId);
// Should ALWAYS be a Dataset!
if (dtype.equals(DvObject.DATASET_DTYPE_STRING)){
this.childToParentIdHash.put(dvId, parentId); // Store the parent child relation
this.addIdNeedingRoleRetrieval(parentId); // We need the roles for this dataverse
this.idToDvObjectType.put(parentId, SearchConstants.SOLR_DATAVERSES); // store the dv object type
}
}
}
private boolean retrieveRoleIdsForDvObjects(DataverseRequest dataverseRequest, AuthenticatedUser au){
String userIdentifier = au.getUserIdentifier();
if (userIdentifier == null){
throw new NullPointerException("RoleTagRetriever.constructor. userIdentifier cannot be null");
}
if (this.idToRoleListHash.isEmpty()){
return true;
}
List<Long> dvObjectIdList = new ArrayList<>(this.idToRoleListHash.keySet());
if (dvObjectIdList.isEmpty()){
return true;
}
//msg("dvObjectIdList: " + dvObjectIdList.toString());
List<Object[]> results = this.roleAssigneeService.getRoleIdsFor(dataverseRequest, dvObjectIdList);
//msgt("runStep1RoleAssignments results: " + results.toString());
if (results == null){
this.addErrorMessage("Sorry, the roleAssigneeService isn't working.");
return false;
}else if (results.isEmpty()){
logger.log(Level.WARNING, "No roles were found for user {0} with ids {1}", new Object[]{userIdentifier, dvObjectIdList.toString()});
this.addErrorMessage("Sorry, no roles were found.");
return false;
}
// Iterate through assigned objects, a single object may end up in
// multiple "buckets"
for (Object[] ra : results) {
Long dvId = (Long)ra[0];
Long roleId = (Long)ra[1];
this.addRoleIdForHash(dvId, roleId);
//msg("dv id: " + dvId + "(" + this.idToDvObjectType.get(dvId) + ") | roleId: "
// + roleId + "(" + this.rolePermissionHelper.getRoleName(roleId)+")");
}
return true;
}
private List<String> getFormattedRoleListForId(Long dvId){
if (dvId==null){
return null;
}
if (!this.idToRoleListHash.containsKey(dvId)){
return null;
}
List<String> roleNames = new ArrayList();
for (Long roleId : this.idToRoleListHash.get(dvId) ){
String roleName = this.rolePermissionHelper.getRoleName(roleId);
if (roleName != null){
roleNames.add(roleName);
}
}
return roleNames;
}
private List<String> getFormattedRoleListForId(Long dvId,
boolean withDatasetPerms,
boolean withFilePerms){
if (dvId==null){
return null;
}
if (!this.idToRoleListHash.containsKey(dvId)){
return null;
}
List<String> roleNames = new ArrayList();
for (Long roleId : this.idToRoleListHash.get(dvId) ){
if ((withDatasetPerms && this.rolePermissionHelper.hasDatasetPermissions(roleId))
|| (withFilePerms && this.rolePermissionHelper.hasFilePermissions(roleId)))
{
String roleName = this.rolePermissionHelper.getRoleName(roleId);
if (roleName != null){
roleNames.add(roleName);
}
}
}
return roleNames;
}
public boolean hasRolesForCard(Long dvObjectId){
if (dvObjectId == null){
return false;
}
return this.finalIdToRolesHash.containsKey(dvObjectId);
}
public List<String> getRolesForCard(Long dvObjectId){
if (!this.hasRolesForCard(dvObjectId)){
return null;
}
return this.finalIdToRolesHash.get(dvObjectId);
}
public JsonArrayBuilder getRolesForCardAsJSON(Long dvObjectId){
if (!this.hasRolesForCard(dvObjectId)){
return null;
}
JsonArrayBuilder jsonArray = Json.createArrayBuilder();
for (String roleName : this.finalIdToRolesHash.get(dvObjectId)){
jsonArray.add(roleName);
}
return jsonArray;
}
/**
* For the cards, make a dict of { dv object id : [role name, role name, etc ]}
*
*/
public void prepareFinalRoleLists(){
msgt("prepareFinalRoleLists");
if (finalCardIds.isEmpty()){
return;
}
List<String> formattedRoleNames;
List<String> finalRoleNames;
for (Long dvIdForCard : this.finalCardIds) {
//msgt("dvIdForCard: " + dvIdForCard + "(" + this.idToDvObjectType.get(dvIdForCard) + ")");
// -------------------------------------------------
// (a) Make a new array with the role names for the card
// -------------------------------------------------
finalRoleNames = new ArrayList();
if (!this.idToDvObjectType.containsKey(dvIdForCard)){
throw new IllegalStateException("All dvObject ids from solr should have their dvObject types in this hash");
}
// -------------------------------------------------
// (b) Add direct role assignments -- may be empty
// -------------------------------------------------
formattedRoleNames = getFormattedRoleListForId(dvIdForCard);
//msg("(a) direct assignments: " + StringUtils.join(formattedRoleNames, ", "));
if (formattedRoleNames != null){
finalRoleNames.addAll(formattedRoleNames);
}
//msg("Roles so far: " + finalRoleNames.toString());
// -------------------------------------------------
// (c) get parent id
// -------------------------------------------------
Long parentId = null;
if (this.childToParentIdHash.containsKey(dvIdForCard)){
parentId = this.childToParentIdHash.get(dvIdForCard);
//msg("(b) parentId: " + parentId);
}else{
// -------------------------------------------------
// No parent! Store roles and move to next id
// -------------------------------------------------
finalIdToRolesHash.put(dvIdForCard, this.formatRoleNames(finalRoleNames));
continue;
}
// -------------------------------------------------
// (d) get dtype
// -------------------------------------------------
String dtype = this.idToDvObjectType.get(dvIdForCard);
switch(dtype){
//case(SearchConstants.SOLR_DATAVERSES // No indirect assignments
case(SearchConstants.SOLR_DATASETS):
// -------------------------------------------------
// (d1) May have indirect assignments re: dataverse
// -------------------------------------------------
formattedRoleNames = getFormattedRoleListForId(parentId, true, true);
if (formattedRoleNames != null){
//msg("(d) indirect assignments: " + StringUtils.join(formattedRoleNames, ", "));
finalRoleNames.addAll(formattedRoleNames);
//msg("Roles from dataverse: " + finalRoleNames.toString());
}
break;
case(SearchConstants.SOLR_FILES):
//msg("(c) FILES");
// -------------------------------------------------
// (d2) May have indirect assignments re: dataset
// -------------------------------------------------
formattedRoleNames = getFormattedRoleListForId(parentId, false, true);
if (formattedRoleNames != null){
//msg("(d) indirect assignments: " + StringUtils.join(formattedRoleNames, ", "));
finalRoleNames.addAll(formattedRoleNames);
}
// May have indirect assignments re: dataverse
//
if (this.childToParentIdHash.containsKey(parentId)){
Long grandparentId = this.childToParentIdHash.get(parentId);
formattedRoleNames = getFormattedRoleListForId(grandparentId, false, true);
if (formattedRoleNames != null){
//msg("(e) 2-step indirect assignments: " + StringUtils.join(formattedRoleNames, ", "));
finalRoleNames.addAll(formattedRoleNames);
}
}
break;
} // end switch
//msg("Roles from dataverse: " + formattedRoleNames.toString());
finalIdToRolesHash.put(dvIdForCard, formatRoleNames(finalRoleNames));
//String key =
//Object value = entry.getValue();
// ...
}
}
private List<String> formatRoleNames(List<String> roleNames){
if (roleNames==null){
return null;
}
// remove duplicates
Set<String> distinctRoleNames = new HashSet<>(roleNames);
// back to list
roleNames = new ArrayList<>(distinctRoleNames);
// sort list
Collections.sort(roleNames);
return roleNames;
}
public boolean hasError(){
return this.errorFound;
}
public String getErrorMessage(){
return this.errorMessage;
}
private void addErrorMessage(String s){
this.errorFound = true;
this.errorMessage = s;
}
private void msg(String s){
//System.out.println(s);
}
private void msgt(String s){
msg("-------------------------------");
msg(s);
msg("-------------------------------");
}
}