diff --git a/src/main/java/edu/harvard/iq/dataverse/DataversePage.java b/src/main/java/edu/harvard/iq/dataverse/DataversePage.java index 76ef1002f7e..93cbb3eed6d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataversePage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataversePage.java @@ -144,6 +144,9 @@ public enum LinkMode { private DualListModel facets = new DualListModel<>(new ArrayList<>(), new ArrayList<>()); private DualListModel featuredDataverses = new DualListModel<>(new ArrayList<>(), new ArrayList<>()); private List dataversesForLinking; + // Depends only on the user's role assignments and on which collection this view is showing, so + // it is computed once per view; setDataverse clears it. + private Boolean showLinkingPopup; private Long linkingDataverseId; private List linkingDVSelectItems; private Dataverse linkingDataverse; @@ -214,7 +217,20 @@ public void setLinkMode(LinkMode linkMode) { this.linkMode = linkMode; } + /** + * Rendered from the page (twice), so it is evaluated repeatedly per request: the answer is + * cached for the view, and the underlying lookups are bounded. Both matter - computing this + * from the full list of permitted collections meant a scan of the dataverse table, and an + * entity per row, on every evaluation. + */ public boolean showLinkingPopup() { + if (showLinkingPopup == null) { + showLinkingPopup = computeShowLinkingPopup(); + } + return showLinkingPopup; + } + + private boolean computeShowLinkingPopup() { // Must be logged in AuthenticatedUser au = getAuthenticatedUser(); if (au == null) { @@ -223,23 +239,26 @@ public boolean showLinkingPopup() { if (dataverse == null) { return false; } + var request = dvRequestService.getDataverseRequest(); // If there is an active search query, that's all that matters (plus having permission on ANY collection) if (query != null && !query.isEmpty()) { - List permitted = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), au, Permission.LinkDataverse); - return permitted != null && !permitted.isEmpty(); + return !permissionService.findSomePermittedCollections(request, au, Permission.LinkDataverse, 1).isEmpty(); } // Otherwise (no active search), check if there is at least one OTHER eligible collection // Eligible means: not the current collection and not in the parent tree // Technically, eligible also means "not already linked", but in that case, we show the Link button anyway and have the Link dialog display a message about all eligible collections already being linked - List dvsWithLinkPermission = permissionService.findPermittedCollections(dvRequestService.getDataverseRequest(), au, Permission.LinkDataverse); - if (dvsWithLinkPermission != null && !dvsWithLinkPermission.isEmpty()) { - List eligibleDataverses = dataverseService.removeUnlinkableDataverses(dvsWithLinkPermission, dataverse, false); - return !eligibleDataverses.isEmpty(); + // The current collection and its parent tree are the only collections removeUnlinkableDataverses + // can drop here, so one candidate more than that tree is enough to tell whether any eligible + // collection exists: if every candidate were dropped, the whole tree would be accounted for and + // the extra one could not have been. + int candidatesNeeded = 2; // the current collection, plus the one that would make the answer yes + for (DvObject owner = dataverse.getOwner(); owner != null; owner = owner.getOwner()) { + candidatesNeeded++; } - - return false; + List candidates = permissionService.findSomePermittedCollections(request, au, Permission.LinkDataverse, candidatesNeeded); + return !dataverseService.removeUnlinkableDataverses(candidates, dataverse, false).isEmpty(); } public void setupLinkingPopup (String popupSetting){ @@ -288,6 +307,7 @@ public Dataverse getDataverse() { public void setDataverse(Dataverse dataverse) { this.dataverse = dataverse; + this.showLinkingPopup = null; } public Long getId() { return this.id; } diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index bd91363d2bb..f9500bd6db9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -970,6 +970,20 @@ public List findPermittedCollections(DataverseRequest request, Authen return null; } + /** + * At most {@code limit} of the collections the user has the permission on, in no particular + * order. Callers that only need to know whether such a collection exists must use this instead + * of {@link #findPermittedCollections(DataverseRequest, AuthenticatedUser, Permission)}, which + * materializes an entity per row and, for a superuser, returns the whole dataverse table. + */ + public List findSomePermittedCollections(DataverseRequest request, AuthenticatedUser user, Permission permission, int limit) { + if (user == null || limit < 1) { + return new ArrayList<>(); + } + var sqlCode = getBaseQueryForAllPermittedDataverses(request, user, 1 << permission.ordinal()) + " LIMIT " + limit; + return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); + } + public boolean hasMultiplePermittedCollections(DataverseRequest request, AuthenticatedUser user, int permissionBit) { if (user != null) { var sqlCode = getBaseQueryForAllPermittedDataverses(request, user, permissionBit) + " LIMIT 2";