diff --git a/doc/release-notes/7804-dv-speedup.md b/doc/release-notes/7804-dv-speedup.md new file mode 100644 index 00000000000..3d096138ad8 --- /dev/null +++ b/doc/release-notes/7804-dv-speedup.md @@ -0,0 +1,7 @@ +### Dataverse Collection Backend Optimizations + +Optimizations to one of the most used pages in the application. + +## Notes for Tool Developers and Integrators + +Mention new section of the Dev Guide that covers writing more efficient front-end code \ No newline at end of file diff --git a/doc/sphinx-guides/source/developers/configuration.rst b/doc/sphinx-guides/source/developers/configuration.rst index 10d581bd634..0eac7de3134 100644 --- a/doc/sphinx-guides/source/developers/configuration.rst +++ b/doc/sphinx-guides/source/developers/configuration.rst @@ -23,7 +23,7 @@ Developers have accessed the simple properties via 1. ``System.getProperty(...)`` for JVM system property settings 2. ``SettingsServiceBean.get(...)`` for database settings and 3. ``SystemConfig.xxx()`` for specially treated settings, maybe mixed from 1 and 2 and other sources. -4. ``SettingsWrapper``, reading from 2 and 3 for use in frontend pages based on JSF +4. ``SettingsWrapper`` must be used to obtain settings from 2 and 3 in frontend JSF (xhtml) pages. Please see the note on how to :ref:`avoid common efficiency issues with JSF render logic expressions `. As of Dataverse Software 5.3, we start to streamline our efforts into using a more consistent approach, also bringing joy and happiness to all the system administrators out there. This will be done by adopting the use of diff --git a/doc/sphinx-guides/source/developers/tips.rst b/doc/sphinx-guides/source/developers/tips.rst index 61dbf73e9ca..206d0327203 100755 --- a/doc/sphinx-guides/source/developers/tips.rst +++ b/doc/sphinx-guides/source/developers/tips.rst @@ -177,6 +177,43 @@ If you already have a working dev environment with Glassfish and want to switch - Copy the "domain1" directory from Glassfish to Payara. +UI Pages Development +-------------------- + +While most of the information in this guide focuses on service and backing beans ("the back end") development in Java, working on JSF/Primefaces xhtml pages presents its own unique challenges. + +.. _avoid-efficiency-issues-with-render-logic-expressions: + +Avoiding Inefficiencies in JSF Render Logic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is important to keep in mind that the expressions in JSF ``rendered=`` attributes may be evaluated **multiple** times. So it is crucial not to use any expressions that require database lookups, or otherwise take any appreciable amount of time and resources. Render attributes should exclusively contain calls to methods in backing beans or caching service wrappers that perform any real work on the first call only, then keep returning the cached result on all the consecutive calls. This way it is irrelevant how many times PrimeFaces may need to call the method as any effect on the performance will be negligible. + +If you are ever in doubt as to how many times the method in your render logic expression is called, you can simply add a logging statement to the method in question. Or you can simply err on the side of assuming that it's going to be called a lot, and ensure that any repeated calls are not expensive to process. + +A simplest, trivial example would be a direct call to a method in SystemConfig service bean. For example, + +`` cachedCvocMap=null; /** * If the dataset version has at least one tabular file. The "hasTabular" @@ -5590,16 +5588,8 @@ public String getLocaleDisplayName(String code) { return displayName; } - public Map getCVocConf() { - //Cache this in the view - if(cachedCvocMap==null) { - cachedCvocMap = fieldService.getCVocConf(false); - } - return cachedCvocMap; - } - public List getVocabScripts() { - return fieldService.getVocabScripts(getCVocConf()); + return fieldService.getVocabScripts(settingsWrapper.getCVocConf()); } public String getFieldLanguage(String languages) { diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java b/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java index b806ef8e22d..1e1353a11fc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java @@ -9,7 +9,6 @@ import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; -import static edu.harvard.iq.dataverse.util.JsfHelper.JH; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.SystemConfig; @@ -40,15 +39,9 @@ public class DataverseHeaderFragment implements java.io.Serializable { @EJB DataverseServiceBean dataverseService; - @EJB - SettingsServiceBean settingsService; - @EJB GroupServiceBean groupService; - @EJB - PermissionServiceBean permissionService; - @EJB SystemConfig systemConfig; @@ -75,7 +68,7 @@ public class DataverseHeaderFragment implements java.io.Serializable { List breadcrumbs = new ArrayList<>(); - private List bannerMessages = new ArrayList<>(); + private List bannerMessages = null; private Long unreadNotificationCount = null; @@ -161,7 +154,7 @@ public Long getUnreadNotificationCount(Long userId){ this.unreadNotificationCount = userNotificationService.getUnreadNotificationCountByUser(userId); }catch (Exception e){ logger.warning("Error trying to retrieve unread notification count for user." + e.getMessage()); - this.unreadNotificationCount = new Long("0"); + this.unreadNotificationCount = 0L; } return this.unreadNotificationCount; } @@ -261,7 +254,7 @@ public String logout() { private Boolean signupAllowed = null; private String redirectToRoot(){ - return "dataverse.xhtml?alias=" + dataverseService.findRootDataverse().getAlias(); + return "dataverse.xhtml?alias=" + settingsWrapper.getRootDataverse().getAlias(); } public boolean isSignupAllowed() { @@ -287,28 +280,32 @@ public boolean isRootDataverseThemeDisabled(Dataverse dataverse) { public List getBannerMessages() { - User user = dataverseSession.getUser(); - AuthenticatedUser au = null; - if (user.isAuthenticated()) { - au = (AuthenticatedUser) user; - } + if (bannerMessages == null) { + bannerMessages = new ArrayList<>(); + + User user = dataverseSession.getUser(); + AuthenticatedUser au = null; + if (user.isAuthenticated()) { + au = (AuthenticatedUser) user; + } - if(au == null){ - bannerMessages = bannerMessageService.findBannerMessages(); - } else{ - bannerMessages = bannerMessageService.findBannerMessages(au.getId()); - } + if (au == null) { + bannerMessages = bannerMessageService.findBannerMessages(); + } else { + bannerMessages = bannerMessageService.findBannerMessages(au.getId()); + } - if (!dataverseSession.getDismissedMessages().isEmpty()) { - for (BannerMessage dismissed : dataverseSession.getDismissedMessages()) { - Iterator itr = bannerMessages.iterator(); - while (itr.hasNext()) { - BannerMessage test = itr.next(); - if (test.equals(dismissed)) { - itr.remove(); + if (!dataverseSession.getDismissedMessages().isEmpty()) { + for (BannerMessage dismissed : dataverseSession.getDismissedMessages()) { + Iterator itr = bannerMessages.iterator(); + while (itr.hasNext()) { + BannerMessage test = itr.next(); + if (test.equals(dismissed)) { + itr.remove(); + } } - } - } + } + } } return bannerMessages; diff --git a/src/main/java/edu/harvard/iq/dataverse/DataversePage.java b/src/main/java/edu/harvard/iq/dataverse/DataversePage.java index 0ec21c5242c..9612c443f87 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataversePage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataversePage.java @@ -318,7 +318,7 @@ public String init() { dataverse = dataverseService.find(this.getId()); } else { try { - dataverse = dataverseService.findRootDataverse(); + dataverse = settingsWrapper.getRootDataverse(); } catch (EJBException e) { // @todo handle case with no root dataverse (a fresh installation) with message about using API to create the root dataverse = null; @@ -330,6 +330,7 @@ public String init() { return permissionsWrapper.notFound(); } if (!dataverse.isReleased() && !permissionService.on(dataverse).has(Permission.ViewUnpublishedDataverse)) { + // the permission lookup above should probably be moved into the permissionsWrapper -- L.A. 5.7 return permissionsWrapper.notAuthorized(); } @@ -340,6 +341,7 @@ public String init() { if (dataverse.getOwner() == null) { return permissionsWrapper.notFound(); } else if (!permissionService.on(dataverse.getOwner()).has(Permission.AddDataverse)) { + // the permission lookup above should probably be moved into the permissionsWrapper -- L.A. 5.7 return permissionsWrapper.notAuthorized(); } @@ -681,7 +683,7 @@ public String save() { if (editMode != null && editMode.equals(EditMode.FEATURED)) { message = BundleUtil.getStringFromBundle("dataverse.feature.update"); } else { - message = (create) ? BundleUtil.getStringFromBundle("dataverse.create.success", Arrays.asList(settingsWrapper.getGuidesBaseUrl(), systemConfig.getGuidesVersion())) : BundleUtil.getStringFromBundle("dataverse.update.success"); + message = (create) ? BundleUtil.getStringFromBundle("dataverse.create.success", Arrays.asList(settingsWrapper.getGuidesBaseUrl(), settingsWrapper.getGuidesVersion())) : BundleUtil.getStringFromBundle("dataverse.update.success"); } JsfHelper.addSuccessMessage(message); @@ -715,7 +717,7 @@ public String cancel() { return "/dataverse.xhtml?alias=" + dataverse.getAlias() + "&faces-redirect=true"; } - public boolean isRootDataverse() { + public boolean isRootDataverse() { return dataverse.getOwner() == null; } @@ -1219,23 +1221,28 @@ public Set> getMetadataLanguages() { return settingsWrapper.getMetadataLanguages(this.dataverse).entrySet(); } + private Set> curationLabelSetOptions = null; + public Set> getCurationLabelSetOptions() { - HashMap setNames = new HashMap(); - Set allowedSetNames = systemConfig.getCurationLabels().keySet(); - if (allowedSetNames.size() > 0) { - // Add an entry for the default (inherited from an ancestor or the system - // default) - String inheritedLabelSet = getCurationLabelSetNameLabel(); - if (!StringUtils.isBlank(inheritedLabelSet)) { - setNames.put(inheritedLabelSet,SystemConfig.DEFAULTCURATIONLABELSET); + if (curationLabelSetOptions == null) { + HashMap setNames = new HashMap(); + Set allowedSetNames = systemConfig.getCurationLabels().keySet(); + if (allowedSetNames.size() > 0) { + // Add an entry for the default (inherited from an ancestor or the system + // default) + String inheritedLabelSet = getCurationLabelSetNameLabel(); + if (!StringUtils.isBlank(inheritedLabelSet)) { + setNames.put(inheritedLabelSet, SystemConfig.DEFAULTCURATIONLABELSET); + } + // Add an entry for disabled + setNames.put(BundleUtil.getStringFromBundle("dataverse.curationLabels.disabled"), SystemConfig.CURATIONLABELSDISABLED); + allowedSetNames.forEach(name -> { + setNames.put(name, name); + }); } - // Add an entry for disabled - setNames.put(BundleUtil.getStringFromBundle("dataverse.curationLabels.disabled"), SystemConfig.CURATIONLABELSDISABLED); - allowedSetNames.forEach(name -> { - setNames.put(name, name); - }); + curationLabelSetOptions = setNames.entrySet(); } - return setNames.entrySet(); + return curationLabelSetOptions; } public String getCurationLabelSetNameLabel() { diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java index d3283fd3945..69404482fce 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponse.java @@ -20,6 +20,13 @@ * * @author skraffmiller */ +@NamedStoredProcedureQuery( + name = "GuestbookResponse.estimateGuestBookResponseTableSize", + procedureName = "estimateGuestBookResponseTableSize", + parameters = { + @StoredProcedureParameter(mode = ParameterMode.OUT, type = Long.class) + } +) @Entity @Table(indexes = { @Index(columnList = "guestbook_id"), diff --git a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java index 2683eaa410f..2bd4e3f3d1d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GuestbookResponseServiceBean.java @@ -33,6 +33,7 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; +import javax.persistence.StoredProcedureQuery; import javax.persistence.TypedQuery; /** @@ -917,7 +918,34 @@ public Long getCountGuestbookResponsesByDatasetId(Long datasetId) { } public Long getCountOfAllGuestbookResponses() { - // dataset id is null, will return 0 + // dataset id is null, will return 0 + + // "SELECT COUNT(*)" is notoriously expensive in PostgresQL for large + // tables. This makes this call fairly expensive for any installation + // with a lot of download/access activity. (This "total download" metrics + // is always displayed when the homepage is loaded). + // It is safe to say that no real life user will need this number to be + // precise down to the last few digits, and/or withing a second, + // especially once that number is in the millions. The + // solution implemented below relies on estimating the number. It is + // very efficient, but also very PostgresQL specific - hence it is + // defined as a custom database function. + // An alternative solution would be to get the exact number every + // hour or so, and cache it centrally for the rest of the application + // somehow. -- L.A. 5.6 + + + try { + StoredProcedureQuery query = this.em.createNamedStoredProcedureQuery("GuestbookResponse.estimateGuestBookResponseTableSize"); + query.execute(); + Long totalCount = (Long) query.getOutputParameterValue(1); + + if (totalCount != null) { + return totalCount; + } + } catch (IllegalArgumentException iae) { + // Don't do anything, we'll fall back to using "SELECT COUNT()" + } Query query = em.createNativeQuery("select count(o.id) from GuestbookResponse o;"); return (Long) query.getSingleResult(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionsWrapper.java b/src/main/java/edu/harvard/iq/dataverse/PermissionsWrapper.java index d255d2ec394..4ee45fc85a1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionsWrapper.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionsWrapper.java @@ -6,12 +6,14 @@ package edu.harvard.iq.dataverse; import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.groups.impl.builtin.AuthenticatedUsers; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.Command; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.engine.command.impl.*; import java.util.HashMap; import java.util.Map; +import java.util.logging.Logger; import javax.ejb.EJB; import javax.faces.view.ViewScoped; import javax.inject.Inject; @@ -24,6 +26,7 @@ @ViewScoped @Named public class PermissionsWrapper implements java.io.Serializable { + private static final Logger logger = Logger.getLogger(PermissionsWrapper.class.getName()); @EJB PermissionServiceBean permissionService; @@ -33,13 +36,25 @@ public class PermissionsWrapper implements java.io.Serializable { @Inject DataverseRequestServiceBean dvRequestService; + + @Inject + SettingsWrapper settingsWrapper; + // This map stores looked up permission results (boolean), for multiple DvObjects + // (referenced by the Long ids), for multiple Commands. The current session + // user is assumed when lookups are performed. private final Map>, Boolean>> commandMap = new HashMap<>(); + + // In some instances our pages need to know whether Authenticated Users as a whole - + // not a specific user! - can perform a specific action on a dataverse + // - such as create datasets or dataverses, in the current dv, or root etc. + // These values can be used in rendered= logic, so we want to cache them too. + private final Map>, Boolean>> authUsersCommandMap = new HashMap<>(); // Maps for caching permissions lookup results: private final Map fileDownloadPermissionMap = new HashMap<>(); // { DvObject.id : Boolean } private final Map datasetPermissionMap = new HashMap<>(); // { Permission human_name : Boolean } - + /** * Check if the current Dataset can Issue Commands * @@ -48,42 +63,39 @@ public class PermissionsWrapper implements java.io.Serializable { * @return {@code true} if the user can issue the command on the object. */ public boolean canIssueCommand(DvObject dvo, Class> command) { - if ((dvo==null) || (dvo.getId()==null)){ - return false; - } - if (command==null){ + if (dvo==null || dvo.getId()==null || command==null){ return false; } - if (commandMap.containsKey(dvo.getId())) { - Map>, Boolean> dvoCommandMap = this.commandMap.get(dvo.getId()); - if (dvoCommandMap.containsKey(command)) { - return dvoCommandMap.get(command); - } else { - return addCommandtoDvoCommandMap(dvo, command, dvoCommandMap); - } + if (checkDvoCacheForCommandAuthorization(dvo.getId(), command, commandMap) == null) { + boolean canIssueCommand = permissionService.requestOn(dvRequestService.getDataverseRequest(), dvo).canIssue(command); + logger.fine("retrieved authorization for " + command.toString() + " on dvo " + dvo.getId()); + addCommandAuthorizationToDvoCache(dvo.getId(), command, commandMap, canIssueCommand); } else { - Map>, Boolean> newDvoCommandMap = new HashMap<>(); - commandMap.put(dvo.getId(), newDvoCommandMap); - return addCommandtoDvoCommandMap(dvo, command, newDvoCommandMap); + logger.fine("using cached authorization for " + command.toString() + " on dvo " + dvo.getId()); } + return checkDvoCacheForCommandAuthorization(dvo.getId(), command, commandMap); } - private boolean addCommandtoDvoCommandMap(DvObject dvo, Class> command, Map>, Boolean> dvoCommandMap) { - if ( dvo==null || (dvo.getId()==null) ){ - return false; + private Boolean checkDvoCacheForCommandAuthorization(Long id, Class> command, Map>, Boolean>> dvoCommandMap) { + if (!dvoCommandMap.containsKey(id)) { + return null; } - if (command==null){ - return false; + if (!dvoCommandMap.get(id).containsKey(command)) { + return null; } - - boolean canIssueCommand; - canIssueCommand = permissionService.requestOn(dvRequestService.getDataverseRequest(), dvo).canIssue(command); - dvoCommandMap.put(command, canIssueCommand); - return canIssueCommand; + return dvoCommandMap.get(id).get(command); } - + + + private void addCommandAuthorizationToDvoCache(Long id, Class> command, Map>, Boolean>> dvoCommandMap, boolean canIssueCommand) { + if (!dvoCommandMap.containsKey(id)) { + dvoCommandMap.put(id, new HashMap<>()); + } + dvoCommandMap.get(id).put(command, canIssueCommand); + } + /* Dataverse Commands */ public boolean canIssueUpdateDataverseCommand(DvObject dvo) { @@ -102,7 +114,11 @@ public boolean canIssueCreateDataverseCommand(DvObject dvo) { return canIssueCommand(dvo, CreateDataverseCommand.class); } - + // TODO: + // Wondering if there's a reason why we don't attempt to cache the + // looked up Permission.* results as well as Command authorizations + // (below). For starters, need to investigate just how much these + // cost us under different scenarios. May be a moot point. -- L.A. 5.7 public boolean canManagePermissions(DvObject dvo) { if (dvo==null || (dvo.getId()==null) ){ return false; @@ -240,9 +256,36 @@ public boolean canIssuePublishDatasetCommand(DvObject dvo){ return canIssueCommand(dvo, PublishDatasetCommand.class); } + // For the dataverse_header fragment (and therefore, most of the pages), + // we need to know if authenticated users can add dataverses and datasets to the + // root collection. For the "Add Data" menu further in the search include fragment + // if the user is not logged in, the page will check if authenticated users can + // add dataverses and datasets in the *current* dataverse. + // These are not very expensive operations - but each take about 10 database queries, + // and it'll add up quickly, if the page keeps evaluating the expressions repeatedly. + // So these values absolutely need to be cached too. + public boolean authenticatedUsersCanIssueCommand(DvObject dvo, Class> command) { + if (dvo == null || dvo.getId() == null || command == null) { + return false; + } + if (checkDvoCacheForCommandAuthorization(dvo.getId(), command, authUsersCommandMap) == null) { + boolean canIssueCommand = permissionService.isUserAllowedOn(AuthenticatedUsers.get(), command, dvo); + logger.fine("retrieved the authorization for authenticated users to issue "+command.toString()); + addCommandAuthorizationToDvoCache(dvo.getId(), command, authUsersCommandMap, canIssueCommand); + } else { + logger.fine("using cached authorization for authenticated users to issue "+command.toString()); + } + return checkDvoCacheForCommandAuthorization(dvo.getId(), command, authUsersCommandMap); + } + public boolean authUsersCanCreateDatasetsInDataverse(Dataverse dataverse) { + return authenticatedUsersCanIssueCommand(dataverse, CreateNewDatasetCommand.class); + } + public boolean authUsersCanCreateDataversesInDataverse(Dataverse dataverse) { + return authenticatedUsersCanIssueCommand(dataverse, CreateDataverseCommand.class); + } // todo: move any calls to this to call NavigationWrapper @Inject NavigationWrapper navigationWrapper; @@ -254,5 +297,4 @@ public String notAuthorized(){ public String notFound() { return navigationWrapper.notFound(); } - } diff --git a/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java b/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java index 32e82907797..a39d4153e5c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java +++ b/src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java @@ -62,6 +62,9 @@ public class SettingsWrapper implements java.io.Serializable { @EJB SystemConfig systemConfig; + + @EJB + DatasetFieldServiceBean fieldService; private Map settingsMap; @@ -71,7 +74,41 @@ public class SettingsWrapper implements java.io.Serializable { private boolean embargoDateChecked = false; private LocalDate maxEmbargoDate = null; - + private String siteUrl = null; + + private Dataverse rootDataverse = null; + + private String guidesVersion = null; + + private String appVersion = null; + + private String appVersionWithBuildNumber = null; + + private Boolean shibPassiveLoginEnabled = null; + + private String footerCopyrightAndYear = null; + + //External Vocabulary support + private Map cachedCvocMap = null; + + private Long zipDownloadLimit = null; + + private Boolean publicInstall = null; + + private Integer uploadMethodsCount; + + private Boolean rsyncUpload = null; + + private Boolean rsyncDownload = null; + + private Boolean httpUpload = null; + + private Boolean rsyncOnly = null; + + private String metricsUrl = null; + + private Boolean dataFilePIDSequentialDependent = null; + public String get(String settingKey) { if (settingsMap == null) { initSettingsMap(); @@ -148,9 +185,7 @@ private void initSettingsMap() { public String getGuidesBaseUrl() { - if (true) - - if (guidesBaseUrl == null) { + if (guidesBaseUrl == null) { String saneDefault = "https://guides.dataverse.org"; guidesBaseUrl = getValueForKey(SettingsServiceBean.Key.GuidesBaseUrl); @@ -169,35 +204,80 @@ public String getGuidesBaseUrl() { } public String getGuidesVersion() { - return systemConfig.getGuidesVersion(); + if (guidesVersion == null) { + String saneDefault = getAppVersion(); + guidesVersion = getValueForKey(SettingsServiceBean.Key.GuidesVersion, saneDefault); + } + return guidesVersion; + } + + public String getDataverseSiteUrl() { + if (siteUrl == null) { + siteUrl = systemConfig.getDataverseSiteUrl(); + } + return siteUrl; } public Long getZipDownloadLimit(){ - return systemConfig.getZipDownloadLimit(); + if (zipDownloadLimit == null) { + String zipLimitOption = getValueForKey(SettingsServiceBean.Key.ZipDownloadLimit); + zipDownloadLimit = SystemConfig.getLongLimitFromStringOrDefault(zipLimitOption, SystemConfig.defaultZipDownloadLimit); + } + return zipDownloadLimit; } public boolean isPublicInstall(){ - return systemConfig.isPublicInstall(); + if (publicInstall == null) { + publicInstall = systemConfig.isPublicInstall(); + } + return publicInstall; } public boolean isRsyncUpload() { - return systemConfig.isRsyncUpload(); + if (rsyncUpload == null) { + rsyncUpload = getUploadMethodAvailable(SystemConfig.FileUploadMethods.RSYNC.toString()); + } + return rsyncUpload; } public boolean isRsyncDownload() { - return systemConfig.isRsyncDownload(); + if (rsyncDownload == null) { + rsyncDownload = systemConfig.isRsyncDownload(); + } + return rsyncDownload; } public boolean isRsyncOnly() { - return systemConfig.isRsyncOnly(); + if (rsyncOnly == null) { + String downloadMethods = getValueForKey(SettingsServiceBean.Key.DownloadMethods); + if(downloadMethods == null){ + rsyncOnly = false; + } else if (!downloadMethods.toLowerCase().equals(SystemConfig.FileDownloadMethods.RSYNC.toString())){ + rsyncOnly = false; + } else { + String uploadMethods = getValueForKey(SettingsServiceBean.Key.UploadMethods); + if (uploadMethods==null){ + rsyncOnly = false; + } else { + rsyncOnly = Arrays.asList(uploadMethods.toLowerCase().split("\\s*,\\s*")).size() == 1 && uploadMethods.toLowerCase().equals(SystemConfig.FileUploadMethods.RSYNC.toString()); + } + } + } + return rsyncOnly; } public boolean isHTTPUpload(){ - return systemConfig.isHTTPUpload(); + if (httpUpload == null) { + httpUpload = getUploadMethodAvailable(SystemConfig.FileUploadMethods.NATIVE.toString()); + } + return httpUpload; } public boolean isDataFilePIDSequentialDependent(){ - return systemConfig.isDataFilePIDSequentialDependent(); + if (dataFilePIDSequentialDependent == null) { + dataFilePIDSequentialDependent = systemConfig.isDataFilePIDSequentialDependent(); + } + return dataFilePIDSequentialDependent; } public String getSupportTeamName() { @@ -213,7 +293,15 @@ public String getSupportTeamEmail() { } public Integer getUploadMethodsCount() { - return systemConfig.getUploadMethodCount(); + if (uploadMethodsCount == null) { + String uploadMethods = getValueForKey(SettingsServiceBean.Key.UploadMethods); + if (uploadMethods==null){ + uploadMethodsCount = 0; + } else { + uploadMethodsCount = Arrays.asList(uploadMethods.toLowerCase().split("\\s*,\\s*")).size(); + } + } + return uploadMethodsCount; } public boolean isRootDataverseThemeDisabled() { @@ -465,6 +553,75 @@ public String getDefaultMetadataLanguage() { return null; } } + + public Dataverse getRootDataverse() { + if (rootDataverse == null) { + rootDataverse = dataverseService.findRootDataverse(); + } + + return rootDataverse; + } + + // The following 2 methods may be unnecessary *with the current implementation* of + // how the application version is retrieved (the values are initialized and cached inside + // SystemConfig once per thread - see the code there); + // But in case we switch to some other method, that requires a database + // lookup like other settings, it should be here. + // This would be a prime candidate for moving into some kind of an + // APPLICATION-scope caching singleton. -- L.A. 5.8 + public String getAppVersion() { + if (appVersion == null) { + appVersion = systemConfig.getVersion(); + } + return appVersion; + } + + public String getAppVersionWithBuildNumber() { + if (appVersionWithBuildNumber == null) { + appVersionWithBuildNumber = systemConfig.getVersion(true); + } + return appVersionWithBuildNumber; + } + + public boolean isShibPassiveLoginEnabled() { + if (shibPassiveLoginEnabled == null) { + shibPassiveLoginEnabled = systemConfig.isShibPassiveLoginEnabled(); + } + return shibPassiveLoginEnabled; + } + + // Caching this result may not be saving much, *currently* (since the value is + // stored in the bundle). -- L.A. 5.8 + public String getFooterCopyrightAndYear() { + if (footerCopyrightAndYear == null) { + footerCopyrightAndYear = systemConfig.getFooterCopyrightAndYear(); + } + return footerCopyrightAndYear; + } + + public Map getCVocConf() { + //Cache this in the view + if(cachedCvocMap==null) { + cachedCvocMap = fieldService.getCVocConf(false); + } + return cachedCvocMap; + } + + public String getMetricsUrl() { + if (metricsUrl == null) { + metricsUrl = getValueForKey(SettingsServiceBean.Key.MetricsUrl); + } + return metricsUrl; + } + + private Boolean getUploadMethodAvailable(String method){ + String uploadMethods = getValueForKey(SettingsServiceBean.Key.UploadMethods); + if (uploadMethods==null){ + return false; + } else { + return Arrays.asList(uploadMethods.toLowerCase().split("\\s*,\\s*")).contains(method); + } + } List allowedExternalStatuses = null; diff --git a/src/main/java/edu/harvard/iq/dataverse/search/SearchIncludeFragment.java b/src/main/java/edu/harvard/iq/dataverse/search/SearchIncludeFragment.java index 38e1a24b4ad..6da4960679d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/search/SearchIncludeFragment.java +++ b/src/main/java/edu/harvard/iq/dataverse/search/SearchIncludeFragment.java @@ -23,8 +23,6 @@ import edu.harvard.iq.dataverse.WidgetWrapper; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.util.BundleUtil; -import edu.harvard.iq.dataverse.util.SystemConfig; - import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; @@ -79,8 +77,6 @@ public class SearchIncludeFragment implements java.io.Serializable { @Inject DataversePage dataversePage; @EJB - SystemConfig systemConfig; - @EJB DatasetFieldServiceBean datasetFieldService; private String browseModeString = "browse"; @@ -292,7 +288,7 @@ The real issue here (https://github.com/IQSS/dataverse/issues/7304) is caused dataversePath = dataverseService.determineDataversePath(this.dataverse); String filterDownToSubtree = SearchFields.SUBTREE + ":\"" + dataversePath + "\""; //logger.info("SUBTREE parameter: " + dataversePath); - if (!this.dataverse.equals(dataverseService.findRootDataverse())) { + if (!(this.dataverse.getOwner() == null)) { /** * @todo centralize this into SearchServiceBean */ @@ -305,7 +301,7 @@ The real issue here (https://github.com/IQSS/dataverse/issues/7304) is caused this.setRootDv(true); } } else { - this.dataverse = dataverseService.findRootDataverse(); + this.dataverse = settingsWrapper.getRootDataverse(); // this.dataverseSubtreeContext = "all"; this.setRootDv(true); } @@ -1348,7 +1344,7 @@ public void setDisplayCardValues() { if (dataverse.getId().equals(result.getParentIdAsLong())) { // definitely NOT linked: result.setIsInTree(true); - } else if (result.getParentIdAsLong() == dataverseService.findRootDataverse().getId()) { + } else if (result.getParentIdAsLong().equals(settingsWrapper.getRootDataverse().getId())) { // the object's parent is the root Dv; and the current // Dv is NOT root... definitely linked: result.setIsInTree(false); diff --git a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java index 033229ac1ba..81504c892fa 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java @@ -113,7 +113,7 @@ public class SystemConfig { * zip file upload. */ private static final int defaultZipUploadFilesLimit = 1000; - private static final long defaultZipDownloadLimit = 104857600L; // 100MB + public static final long defaultZipDownloadLimit = 104857600L; // 100MB private static final int defaultMultipleUploadFilesLimit = 1000; private static final int defaultLoginSessionTimeout = 480; // = 8 hours @@ -131,6 +131,11 @@ public String getVersion() { return getVersion(false); } + // The return value is a "prviate static String", that should be initialized + // once, on the first call (see the code below)... But this is a @Stateless + // bean... so that would mean "once per thread"? - this would be a prime + // candidate for being moved into some kind of an application-scoped caching + // service... some CachingService @Singleton - ? (L.A. 5.8) public String getVersion(boolean withBuildNumber) { if (appVersionString == null) { @@ -417,7 +422,7 @@ public String getMetricsUrl() { return metricsUrl; } - static long getLongLimitFromStringOrDefault(String limitSetting, Long defaultValue) { + public static long getLongLimitFromStringOrDefault(String limitSetting, Long defaultValue) { Long limit = null; if (limitSetting != null && !limitSetting.equals("")) { @@ -534,7 +539,7 @@ public String getApplicationTermsOfUse() { // is no language-specific value String appTermsOfUse = settingsService.getValueForKey(SettingsServiceBean.Key.ApplicationTermsOfUse, saneDefaultForAppTermsOfUse); //Now get the language-specific value if it exists - if (!language.equalsIgnoreCase(BundleUtil.getDefaultLocale().getLanguage())) { + if (language != null && !language.equalsIgnoreCase(BundleUtil.getDefaultLocale().getLanguage())) { appTermsOfUse = settingsService.getValueForKey(SettingsServiceBean.Key.ApplicationTermsOfUse, language, appTermsOfUse); } return appTermsOfUse; diff --git a/src/main/resources/db/migration/V5.8.0.3__7804-optimizations.sql b/src/main/resources/db/migration/V5.8.0.3__7804-optimizations.sql new file mode 100644 index 00000000000..810f6220d94 --- /dev/null +++ b/src/main/resources/db/migration/V5.8.0.3__7804-optimizations.sql @@ -0,0 +1,30 @@ +-- This creates a function that ESTIMATES the size of the +-- GuestbookResponse table (for the metrics display), instead +-- of relying on straight "SELECT COUNT(*) ..." +-- Significant potential savings for an active installation. + +CREATE OR REPLACE FUNCTION estimateGuestBookResponseTableSize() +RETURNS bigint AS $$ +DECLARE + estimatedsize bigint; +BEGIN + SELECT CASE WHEN relpages=0 THEN 0 + ELSE ((reltuples / relpages) + * (pg_relation_size('public.guestbookresponse') / current_setting('block_size')::int))::bigint + END + FROM pg_class + WHERE oid = 'public.guestbookresponse'::regclass INTO estimatedsize; + + if estimatedsize = 0 then + SELECT COUNT(id) FROM guestbookresponse INTO estimatedsize; + END if; + + RETURN estimatedsize; +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +-- Extra indexes for the DatasetVersion table +CREATE INDEX index_datasetversion_termsofuseandaccess_id ON datasetversion (termsofuseandaccess_id); +-- TODO: what else? + + diff --git a/src/main/webapp/dataverse.xhtml b/src/main/webapp/dataverse.xhtml index cc1a1e8ed59..bca2697f90f 100644 --- a/src/main/webapp/dataverse.xhtml +++ b/src/main/webapp/dataverse.xhtml @@ -138,7 +138,7 @@
-
#{systemConfig.dataverseSiteUrl}/dataverse/
+
#{settingsWrapper.dataverseSiteUrl}/dataverse/
- + #{bundle['metrics.title']} - + #{bundle['metrics.title']}
@@ -701,7 +701,7 @@

#{bundle['dataverse.share.dataverseShare.tip']}

-
+
@@ -73,7 +73,7 @@
  • - +
  • @@ -166,7 +166,7 @@
  • - +
  • diff --git a/src/main/webapp/dataverse_template.xhtml b/src/main/webapp/dataverse_template.xhtml index ddb02ebd19b..8cb60eaa6cc 100644 --- a/src/main/webapp/dataverse_template.xhtml +++ b/src/main/webapp/dataverse_template.xhtml @@ -32,13 +32,13 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + + - - +   +