diff --git a/WebRoot/WEB-INF/error.jsp b/WebRoot/WEB-INF/error.jsp new file mode 100644 index 0000000..448648b --- /dev/null +++ b/WebRoot/WEB-INF/error.jsp @@ -0,0 +1,39 @@ +<%@ 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 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); +%> + + + + + UWPR - Something went wrong + + +
+

Something went wrong

+

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 %>

+

Return to the home page

+
+ + diff --git a/WebRoot/WEB-INF/web.xml b/WebRoot/WEB-INF/web.xml index 58a9dd9..e3ae73d 100644 --- a/WebRoot/WEB-INF/web.xml +++ b/WebRoot/WEB-INF/web.xml @@ -44,6 +44,20 @@ + + + java.lang.Throwable + /WEB-INF/error.jsp + + + 500 + /WEB-INF/error.jsp + + + /tags/struts-bean 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) - -
- - +
diff --git a/src/PRMessageResources.properties b/src/PRMessageResources.properties index 349f47d..0efe01d 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 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. 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"));