Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions WebRoot/WEB-INF/error.jsp
Original file line number Diff line number Diff line change
@@ -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 <error-page> 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);
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<link rel="stylesheet" type="text/css" href="/pr/css/global.css"/>
<title>UWPR - Something went wrong</title>
</head>
<body>
<div style="max-width: 600px; margin: 40px auto; padding: 0 20px;">
<h2>Something went wrong</h2>
<p>An error on the server stopped this request from completing. Please try again.</p>
<p>If it keeps happening, email the UWPR administrators and quote the reference below so they can
find the matching log entry.</p>
<p><b>Reference:</b> <%= reference %></p>
<p><a href="/pr/">Return to the home page</a></p>
</div>
</body>
</html>
14 changes: 14 additions & 0 deletions WebRoot/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@
</welcome-file-list>


<!-- Generic error page. Keeps an uncaught exception, and any SQL a SQLException carries, from
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. -->
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/WEB-INF/error.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/error.jsp</location>
</error-page>


<!-- Struts Tag Library Descriptors -->
<taglib>
<taglib-uri>/tags/struts-bean</taglib-uri>
Expand Down
38 changes: 14 additions & 24 deletions WebRoot/pages/internal/front.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -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
// <details> and both buttons start enabled, so neither depends on this running.
if ($.fn.tablesorter) {
$("#your_projects_table").tablesorter(sorterOptions);
$("#archived_projects_table").tablesorter(sorterOptions);
Expand Down Expand Up @@ -251,7 +244,7 @@ consequently, will NOT work with either pathogenic or radioactive materials.
</TABLE>

<div style="margin-top: 8px;">
<input type="submit" id="archive_selected_button" value="Archive Selected" disabled="disabled"/>
<input type="submit" id="archive_selected_button" value="Archive Selected"/>
&nbsp;&nbsp;
<span style="font-size: 8pt;">
<a href="#" id="select_all_active">select all</a> /
Expand Down Expand Up @@ -285,14 +278,11 @@ consequently, will NOT work with either pathogenic or radioactive materials.

<yrcwww:contentbox title="Archived Projects" innerBox="true">

<div style="margin-bottom: 8px;">
<a href="#" id="toggle_archived" style="font-weight: bold;">show</a>
<span style="font-size: 8pt; color: #666;">
&nbsp;(<bean:size id="archivedCount" name="archivedProjects"/><bean:write name="archivedCount"/> archived)
</span>
</div>

<div id="archived_projects_section" style="display: none;">
<bean:size id="archivedCount" name="archivedProjects"/>
<details style="margin-top: 4px;">
<summary style="cursor: pointer; font-weight: bold;">Show or hide
<span style="font-size: 8pt; color: #666; font-weight: normal;">&nbsp;(<bean:write name="archivedCount"/> archived)</span>
</summary>
<form action="/pr/archiveProjects.do" method="post">
<input type="hidden" name="archived" value="false"/>

Expand Down Expand Up @@ -342,7 +332,7 @@ consequently, will NOT work with either pathogenic or radioactive materials.
</TABLE>

<div style="margin-top: 8px;">
<input type="submit" id="unarchive_selected_button" value="Unarchive Selected" disabled="disabled"/>
<input type="submit" id="unarchive_selected_button" value="Unarchive Selected"/>
&nbsp;&nbsp;
<span style="font-size: 8pt;">
<a href="#" id="select_all_archived">select all</a> /
Expand All @@ -351,7 +341,7 @@ consequently, will NOT work with either pathogenic or radioactive materials.
</div>

</form>
</div>
</details>

</yrcwww:contentbox>
</logic:notEmpty>
Expand Down
1 change: 1 addition & 0 deletions src/PRMessageResources.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
13 changes: 10 additions & 3 deletions src/org/yeastrc/www/project/ArchiveProjectsAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,28 @@ public ActionForward execute( ActionMapping mapping,
boolean deniedAny = false;
boolean failedAny = false;
boolean hasFutureTimeAny = false;
boolean skippedAny = false;

for (String projectIdStr: projectIds) {

int projectId;
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;
}

Project project;
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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -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"));
Expand Down