/* * 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("-------------------------------"); } }