From c63d1c85bc79d1fe899540cbe99eca85dbcaa516 Mon Sep 17 00:00:00 2001 From: Vagisha Sharma Date: Sun, 7 Jun 2026 15:41:16 -0700 Subject: [PATCH 1/2] Gated library spectra for guests on large spectrum libraries * Spectrum viewer pages were slow (6-23 s) for peptides in large .elib libraries on network storage (GPFS). The load came from anonymous web crawlers * Added LibrarySpectrumMatchGetter.blockSpectraForGuest (guest user + an associated .elib/.blib library file >= 500 MB) * TargetedMSController.addSpectrumViews and LibrarySpectrumDataAction now show the existing "log in to view" prompt (login-required error for the AJAX path) instead of reading the library * ChromatogramDataset.getPeptideIdRetentionTimes skips the peptide-ID retention-time markers for gated guests * Mitigation for guest/bot traffic only. Logged-in users and small libraries unchanged Co-Authored-By: Claude --- .../targetedms/TargetedMSController.java | 34 +++++++++++ .../targetedms/chart/ChromatogramDataset.java | 7 +++ .../spectrum/LibrarySpectrumMatchGetter.java | 56 +++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/src/org/labkey/targetedms/TargetedMSController.java b/src/org/labkey/targetedms/TargetedMSController.java index 7ae73decc..03983eee2 100644 --- a/src/org/labkey/targetedms/TargetedMSController.java +++ b/src/org/labkey/targetedms/TargetedMSController.java @@ -3232,8 +3232,31 @@ public void addNavTrail(NavTree root) } } + /** + * Reading library spectra from a large library file can take many seconds over network storage, especially for large + * EncyclopeDIA libraries. To protect public folders from aggressive bots, do not show library spectra to guests when + * the library is large. Show the login prompt instead. + * Returns true (and adds the login view) when the library spectrum should be withheld. + */ + private boolean addGuestSpectrumGate(TargetedMSRun run, VBox vbox) + { + if (!LibrarySpectrumMatchGetter.blockSpectraForGuest(getUser(), run.getId())) + { + return false; + } + HtmlView loginView = getLoginView(getViewContext(), getContainer()); + loginView.setTitle("Library Spectrum"); + loginView.setFrame(WebPartView.FrameType.PORTAL); + vbox.addView(loginView); + return true; + } + private void addSpectrumViews(TargetedMSRun run, VBox vbox, Precursor precursor, BindException errors) { + if (addGuestSpectrumGate(run, vbox)) + { + return; + } PipeRoot root = PipelineService.get().getPipelineRootSetting(getContainer()); if (null != root) { @@ -3252,6 +3275,10 @@ private void addSpectrumViews(TargetedMSRun run, VBox vbox, Precursor precursor, private void addSpectrumViews(TargetedMSRun run, VBox vbox, Peptide peptide, BindException errors) { + if (addGuestSpectrumGate(run, vbox)) + { + return; + } PipeRoot root = PipelineService.get().getPipelineRootSetting(getContainer()); if (null != root) { @@ -3336,6 +3363,13 @@ public Object execute(SpectrumDataForm form, BindException errors) } TargetedMSRun run = TargetedMSManager.getRunForGeneralMolecule(peptide.getId()); + // Apply the same guest gate as the spectrum views (see LibrarySpectrumMatchGetter.blockSpectraForGuest). + if (LibrarySpectrumMatchGetter.blockSpectraForGuest(getUser(), run.getId())) + { + response.put("error", "Please log in to view spectra for large libraries."); + return response; + } + List libraries = LibraryManager.getLibraries(run.getId()); PeptideSettings.SpectrumLibrary library = null; for (PeptideSettings.SpectrumLibrary lib : libraries) diff --git a/src/org/labkey/targetedms/chart/ChromatogramDataset.java b/src/org/labkey/targetedms/chart/ChromatogramDataset.java index afeba8579..c78fd36cc 100644 --- a/src/org/labkey/targetedms/chart/ChromatogramDataset.java +++ b/src/org/labkey/targetedms/chart/ChromatogramDataset.java @@ -901,6 +901,13 @@ public void build() protected List getPeptideIdRetentionTimes() { + // Skip peptide-ID retention-time markers for guests when the library is large. Reading large libraries can be slow over network storage. + // See LibrarySpectrumMatchGetter.blockSpectraForGuest. + if (LibrarySpectrumMatchGetter.blockSpectraForGuest(_user, _run.getId())) + { + return Collections.emptyList(); + } + SampleFile sampleFile = ReplicateManager.getSampleFile(_pChromInfo.getSampleFileId()); // TODO: May want to move LocalDirectory up to controller, where others are created. Sharing probably desired. diff --git a/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java b/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java index 531e9f161..55bebda36 100644 --- a/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java +++ b/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java @@ -16,6 +16,7 @@ package org.labkey.targetedms.view.spectrum; import org.apache.commons.io.FilenameUtils; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.cache.BlockingCache; @@ -24,6 +25,7 @@ import org.labkey.api.data.Container; import org.labkey.api.security.User; import org.labkey.api.util.FileUtil; +import org.labkey.api.util.logging.LogHelper; import org.labkey.targetedms.TargetedMSManager; import org.labkey.targetedms.TargetedMSRun; import org.labkey.targetedms.TargetedMSSchema; @@ -42,6 +44,8 @@ import org.labkey.targetedms.query.PeptideManager; import org.labkey.targetedms.query.PrecursorManager; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.sql.SQLException; import java.util.ArrayList; @@ -59,8 +63,60 @@ */ public class LibrarySpectrumMatchGetter { + private static final Logger LOG = LogHelper.getLogger(LibrarySpectrumMatchGetter.class, "Matches library spectra and retention times for the library spectrum viewer"); + private static final int CACHE_SIZE = 10; + // Reading library spectra and retention times from large spectrum libraries can be slow over network storage. + // For EncyclopeDIA .elib we read one row per source file for the peptide. This can be hundreds of rows and the needed + // columns are not in the index, so each table row lookup is a separate network round-trip on GPFS. + // For BiblioSpec .blib we scan the unindexed RetentionTimes table for the RT of the peptide in all the scans and source + // files. + // PanoramaWeb has large files of both types, so the size gate covers both library types. To protect public folders from + // aggressive bots, library spectra are not shown to guests when the library file is at or above this size. Guests are + // asked to log in instead. + private static final long GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT = 500L * 1024 * 1024; // 500 MB + + /** + * Returns true if library spectra should NOT be shown to the given user for the given run, + * i.e. the user is a guest and the run references a supported spectrum library file that is at + * or above {@link #GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT}. Logged-in users are never blocked, and + * small libraries are read in place as before. + */ + public static boolean blockSpectraForGuest(User user, long runId) + { + if (!user.isGuest()) + { + return false; + } + for (Path libPath : LibraryManager.getLibraryFilePaths(runId).values()) + { + if (isLargeSpectrumLibrary(libPath)) + { + return true; + } + } + return false; + } + + private static boolean isLargeSpectrumLibrary(Path libPath) + { + // Only .elib/.blib libraries are read for spectra; ignore anything we cannot read. + if (libPath == null || getReaderForLibrary(FileUtil.getFileName(libPath)) == null) + { + return false; + } + try + { + return Files.exists(libPath) && Files.size(libPath) >= GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT; + } + catch (IOException e) + { + LOG.warn("Could not determine size of spectrum library file " + libPath, e); + return false; + } + } + private static final BlockingCache> _peptideIdRtsCache = CacheManager.getBlockingCache(CACHE_SIZE, CacheManager.DAY, "TargetedMS peptide ID retention times", (precursor, argument) -> { From dd71d23a4524a420b0fee940b4629691124df02f Mon Sep 17 00:00:00 2001 From: Vagisha Sharma Date: Sun, 7 Jun 2026 19:50:29 -0700 Subject: [PATCH 2/2] Addressed Copilot review feedback * Removed redundant Files.exists() check in isLargeSpectrumLibrary; Files.size() already throws for a missing file, so the extra call was a needless second filesystem round-trip on network storage. * Matched the spectrum AJAX login-required error text to the existing "Login to view this data" wording from getLoginView(). Co-Authored-By: Claude --- src/org/labkey/targetedms/TargetedMSController.java | 2 +- .../view/spectrum/LibrarySpectrumMatchGetter.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSController.java b/src/org/labkey/targetedms/TargetedMSController.java index 03983eee2..d75d0d8c7 100644 --- a/src/org/labkey/targetedms/TargetedMSController.java +++ b/src/org/labkey/targetedms/TargetedMSController.java @@ -3366,7 +3366,7 @@ public Object execute(SpectrumDataForm form, BindException errors) // Apply the same guest gate as the spectrum views (see LibrarySpectrumMatchGetter.blockSpectraForGuest). if (LibrarySpectrumMatchGetter.blockSpectraForGuest(getUser(), run.getId())) { - response.put("error", "Please log in to view spectra for large libraries."); + response.put("error", "Login to view this data"); return response; } diff --git a/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java b/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java index 55bebda36..cd5e4178a 100644 --- a/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java +++ b/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java @@ -108,10 +108,14 @@ private static boolean isLargeSpectrumLibrary(Path libPath) } try { - return Files.exists(libPath) && Files.size(libPath) >= GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT; + // Files.size throws NoSuchFileException if the file is missing, so a separate Files.exists + // check is unnecessary and would add a second filesystem round-trip on network storage. + return Files.size(libPath) >= GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT; } catch (IOException e) { + // If we cannot stat the file it is missing or unreadable, in which case the + // downstream library read will fail too. LOG.warn("Could not determine size of spectrum library file " + libPath, e); return false; }