From c8d3b283c06ce13da9cf1e2da3ebf7adefbb9a24 Mon Sep 17 00:00:00 2001 From: Vagisha Sharma Date: Sat, 12 Sep 2026 01:10:17 -0700 Subject: [PATCH 1/5] Reported the projects a bulk archive skipped instead of succeeding silently ArchiveProjectsAction.execute skipped a projectIds value that would not parse or would not load, logged a warning and carried on, and the request still reported success. A researcher who selected ten projects and archived nine was told nothing. The 11 billed projects with no tblBilledProject row are exactly this case. Track a skippedAny flag alongside deniedAny, failedAny and hasFutureTimeAny, and add error.project.archiveskipped to the report, so a partial batch says so. Not tested. See TESTS in ai-uwpr-webapp. Co-Authored-By: Claude --- src/PRMessageResources.properties | 1 + .../yeastrc/www/project/ArchiveProjectsAction.java | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/PRMessageResources.properties b/src/PRMessageResources.properties index 349f47d..7932c0d 100644 --- a/src/PRMessageResources.properties +++ b/src/PRMessageResources.properties @@ -126,6 +126,7 @@ error.project.archivefailed.one=The project could not be updated and was left un error.project.archivefailed.some=Some projects could not be updated and were left unchanged. error.project.archivehasfuturetime=Projects with instrument time scheduled in the future cannot be archived. error.project.archivenodirection=Parameter to archive or unarchive is missing or not valid, so nothing was changed. +error.project.archiveskipped=Some of the selected projects could not be found and were left unchanged. error.project.instrumenttimecheckfailed=Checking for scheduled instrument time failed, so the project was not deleted. error.project.deletefailed=Deleting the project failed. Check the project before trying again. diff --git a/src/org/yeastrc/www/project/ArchiveProjectsAction.java b/src/org/yeastrc/www/project/ArchiveProjectsAction.java index 081643f..d2edc7b 100644 --- a/src/org/yeastrc/www/project/ArchiveProjectsAction.java +++ b/src/org/yeastrc/www/project/ArchiveProjectsAction.java @@ -76,6 +76,7 @@ public ActionForward execute( ActionMapping mapping, boolean deniedAny = false; boolean failedAny = false; boolean hasFutureTimeAny = false; + boolean skippedAny = false; for (String projectIdStr: projectIds) { @@ -83,8 +84,9 @@ public ActionForward execute( ActionMapping mapping, try { projectId = Integer.parseInt(projectIdStr); } catch (NumberFormatException e) { - // Ignore anything that is not a project ID and keep going with the rest. + // Not a project ID. Skip it and report the selection was not fully applied. log.warn("Ignoring non-numeric projectIds value in archive request: " + projectIdStr); + skippedAny = true; continue; } @@ -92,8 +94,10 @@ public ActionForward execute( ActionMapping mapping, try { project = ProjectFactory.getProject(projectId); } catch (Exception e) { - // Project is gone or of an unknown type. Skip it rather than failing the batch. + // Project is gone or of an unknown type. Skip it and report it rather than + // letting the batch succeed silently. log.warn("Could not load project " + projectId + " while archiving", e); + skippedAny = true; continue; } @@ -134,7 +138,7 @@ public ActionForward execute( ActionMapping mapping, } } - boolean anythingToReport = deniedAny || failedAny || hasFutureTimeAny; + boolean anythingToReport = deniedAny || failedAny || hasFutureTimeAny || skippedAny; if (anythingToReport) { // The project page archives one project, the home page buttons archive a selection. @@ -147,6 +151,9 @@ public ActionForward execute( ActionMapping mapping, if (hasFutureTimeAny) { errors.add("archive", new ActionMessage("error.project.archivehasfuturetime")); } + if (skippedAny) { + errors.add("archive", new ActionMessage("error.project.archiveskipped")); + } if (failedAny) { errors.add("archive", new ActionMessage(single ? "error.project.archivefailed.one" : "error.project.archivefailed.some")); From f8ccec7186335c42d51e6a20f2bf869b34407cc5 Mon Sep 17 00:00:00 2001 From: Vagisha Sharma Date: Sat, 12 Sep 2026 01:10:25 -0700 Subject: [PATCH 2/5] Made the archived project list and its bulk buttons work without JavaScript front.jsp hid the archived list in a div revealed only by the #toggle_archived handler, and started both bulk buttons disabled and enabled them only from updateArchiveButton. With JavaScript off the archived list, Unarchive Selected and Archive Selected never appeared or enabled. * The archived list is now a native
/, which expands and collapses with no script. The #toggle_archived handler is gone * Both submit buttons start enabled in the HTML. The script still disables each until a checkbox in its table is checked, as an enhancement. Submitting an empty selection is already handled by ArchiveProjectsAction Not tested. See TESTS in ai-uwpr-webapp. Co-Authored-By: Claude --- WebRoot/pages/internal/front.jsp | 38 ++++++++++++-------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/WebRoot/pages/internal/front.jsp b/WebRoot/pages/internal/front.jsp index cdc85cb..05701cf 100644 --- a/WebRoot/pages/internal/front.jsp +++ b/WebRoot/pages/internal/front.jsp @@ -97,22 +97,15 @@ $(document).ready(function() { return false; }); - // Archived list starts collapsed, since the point is to keep it out of the way. - $("#toggle_archived").click(function() { - var section = $("#archived_projects_section"); - section.toggle(); - $(this).text(section.is(":visible") ? "hide" : "show"); - return false; - }); - - // Initial state, in case the browser restored checked boxes on a back-navigation. + // Both buttons are enabled in the HTML, so they work with no JavaScript. Here they are + // disabled until a checkbox in their table is checked. This also covers a browser + // restoring checked boxes on a back-navigation. updateArchiveButton("your_projects_table", "archive_selected_button"); updateArchiveButton("archived_projects_table", "unarchive_selected_button"); - // Last, and guarded. Sorting is the only thing here that the page can do without. - // If the plugin fails to load, an unguarded call throws and everything after it in - // this function is skipped, which would leave the archived list permanently collapsed - // and both buttons disabled. + // Last, and guarded. Sorting is the only thing here the page can do without, so a missing + // plugin must not throw and skip the handlers bound above. The archived list is a native + //
and both buttons start enabled, so neither depends on this running. if ($.fn.tablesorter) { $("#your_projects_table").tablesorter(sorterOptions); $("#archived_projects_table").tablesorter(sorterOptions); @@ -251,7 +244,7 @@ consequently, will NOT work with either pathogenic or radioactive materials.
- +    select all / @@ -285,14 +278,11 @@ consequently, will NOT work with either pathogenic or radioactive materials. -
- show - -  ( archived) - -
- - +
From 0f4e5c2f98258ec64abbb28d3a712108a83acfb0 Mon Sep 17 00:00:00 2001 From: Vagisha Sharma Date: Sat, 12 Sep 2026 01:12:32 -0700 Subject: [PATCH 3/5] Stopped an uncaught exception returning its detail to the browser A SQLException, or any uncaught exception, reached the browser through the container's default error page, which prints the exception message. For the project search that message was the SQL itself, with its table aliases and column list. The search injection is already bound (PR #9), but any other SQLException on any page leaks the same way. * Added WebRoot/error.jsp, a self-contained page that logs the detail with a reference and shows the user only a generic message and that reference * Registered it in web.xml for java.lang.Throwable and for status 500. DTD 2.3 requires the error-page entries after welcome-file-list and before the taglibs Not tested. See TESTS in ai-uwpr-webapp. Co-Authored-By: Claude --- WebRoot/WEB-INF/web.xml | 13 +++++++++++++ WebRoot/error.jsp | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 WebRoot/error.jsp diff --git a/WebRoot/WEB-INF/web.xml b/WebRoot/WEB-INF/web.xml index 58a9dd9..5bebdbf 100644 --- a/WebRoot/WEB-INF/web.xml +++ b/WebRoot/WEB-INF/web.xml @@ -44,6 +44,19 @@ + + + java.lang.Throwable + /error.jsp + + + 500 + /error.jsp + + + /tags/struts-bean diff --git a/WebRoot/error.jsp b/WebRoot/error.jsp new file mode 100644 index 0000000..0f4ffe7 --- /dev/null +++ b/WebRoot/error.jsp @@ -0,0 +1,38 @@ +<%@ page isErrorPage="true" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> +<%@ page import="org.apache.logging.log4j.LogManager,org.apache.logging.log4j.Logger" %> +<%-- + Generic error page, reached from the entries in web.xml for an uncaught exception or + a 500. The failure detail is logged on the server. Nothing about it, in particular any SQL a + SQLException carries, reaches the browser. Kept self-contained so it cannot itself throw and turn + into an error loop. +--%> +<% + Throwable failure = (Throwable) request.getAttribute("javax.servlet.error.exception"); + Object failedUri = request.getAttribute("javax.servlet.error.request_uri"); + Object statusCode = request.getAttribute("javax.servlet.error.status_code"); + + // A short reference the user can quote. It is logged beside the detail, and the log's own + // timestamp matches it, so a report can be traced to the failure. + String reference = Long.toHexString(System.currentTimeMillis()); + + Logger errorLog = LogManager.getLogger("org.uwpr.www.ErrorPage"); + errorLog.error("Unhandled error [" + reference + "] on " + failedUri + " (status " + statusCode + ")", failure); +%> + + + + + UWPR - Something went wrong + + +
+

Something went wrong

+

An error on the server stopped this page from completing. Please try again.

+

If it keeps happening, email the UWPR administrators and quote the reference below so they can + find the matching log entry.

+

Reference: <%= reference %>

+

Return to the home page

+
+ + From 7d9e47dacc1533283d293764c73a6f55847ca16a Mon Sep 17 00:00:00 2001 From: Vagisha Sharma Date: Sun, 13 Sep 2026 20:58:31 -0700 Subject: [PATCH 4/5] error.jsp wording edit --- WebRoot/error.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebRoot/error.jsp b/WebRoot/error.jsp index 0f4ffe7..cdc38f3 100644 --- a/WebRoot/error.jsp +++ b/WebRoot/error.jsp @@ -28,7 +28,7 @@

Something went wrong

-

An error on the server stopped this page from completing. Please try again.

+

An error on the server stopped this request from completing. Please try again.

If it keeps happening, email the UWPR administrators and quote the reference below so they can find the matching log entry.

Reference: <%= reference %>

From 314167f1d8a4ff85a824037d790e9709dd2cebdc Mon Sep 17 00:00:00 2001 From: Vagisha Sharma Date: Sun, 13 Sep 2026 21:41:02 -0700 Subject: [PATCH 5/5] Addressed Copilot review on PR #11 * Moved error.jsp under WEB-INF so only a container error dispatch reaches it. At the web root a direct request logged a spurious ErrorPage entry. * Used a UUID for the error reference instead of currentTimeMillis, which two failures in the same millisecond could share. * Reworded error.project.archiveskipped from "could not be found" to "could not be loaded", since the skip also covers a load failure or an invalid project type. Co-Authored-By: Claude --- WebRoot/{ => WEB-INF}/error.jsp | 7 ++++--- WebRoot/WEB-INF/web.xml | 9 +++++---- src/PRMessageResources.properties | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) rename WebRoot/{ => WEB-INF}/error.jsp (84%) diff --git a/WebRoot/error.jsp b/WebRoot/WEB-INF/error.jsp similarity index 84% rename from WebRoot/error.jsp rename to WebRoot/WEB-INF/error.jsp index cdc38f3..448648b 100644 --- a/WebRoot/error.jsp +++ b/WebRoot/WEB-INF/error.jsp @@ -11,9 +11,10 @@ Object failedUri = request.getAttribute("javax.servlet.error.request_uri"); Object statusCode = request.getAttribute("javax.servlet.error.status_code"); - // A short reference the user can quote. It is logged beside the detail, and the log's own - // timestamp matches it, so a report can be traced to the failure. - String reference = Long.toHexString(System.currentTimeMillis()); + // A unique reference the user can quote. It is logged beside the detail, so a report can be + // traced to the failure. A UUID rather than a timestamp, so two failures in the same + // millisecond cannot share a reference. + String reference = java.util.UUID.randomUUID().toString(); Logger errorLog = LogManager.getLogger("org.uwpr.www.ErrorPage"); errorLog.error("Unhandled error [" + reference + "] on " + failedUri + " (status " + statusCode + ")", failure); diff --git a/WebRoot/WEB-INF/web.xml b/WebRoot/WEB-INF/web.xml index 5bebdbf..e3ae73d 100644 --- a/WebRoot/WEB-INF/web.xml +++ b/WebRoot/WEB-INF/web.xml @@ -45,15 +45,16 @@ + reaching the browser. The detail is logged server-side. It lives under WEB-INF so only a + container error dispatch reaches it, never a direct request that would log a spurious error. + DTD 2.3 requires error-page here, after welcome-file-list and before the taglibs. --> java.lang.Throwable - /error.jsp + /WEB-INF/error.jsp 500 - /error.jsp + /WEB-INF/error.jsp diff --git a/src/PRMessageResources.properties b/src/PRMessageResources.properties index 7932c0d..0efe01d 100644 --- a/src/PRMessageResources.properties +++ b/src/PRMessageResources.properties @@ -126,7 +126,7 @@ error.project.archivefailed.one=The project could not be updated and was left un error.project.archivefailed.some=Some projects could not be updated and were left unchanged. error.project.archivehasfuturetime=Projects with instrument time scheduled in the future cannot be archived. error.project.archivenodirection=Parameter to archive or unarchive is missing or not valid, so nothing was changed. -error.project.archiveskipped=Some of the selected projects could not be found and were left unchanged. +error.project.archiveskipped=Some of the selected projects could not be loaded and were left unchanged. error.project.instrumenttimecheckfailed=Checking for scheduled instrument time failed, so the project was not deleted. error.project.deletefailed=Deleting the project failed. Check the project before trying again.