fixes wrong handling of feeds - only one channel per feed is allowed

Thu, 13 May 2021 19:31:09 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 13 May 2021 19:31:09 +0200
changeset 198
94f174d591ab
parent 197
0a2ad22ac656
child 199
59393c8cc557

fixes wrong handling of feeds - only one channel per feed is allowed

src/main/kotlin/de/uapcore/lightpit/Constants.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt file | annotate | diff | comparison | revisions
src/main/resources/localization/strings.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/strings_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issues-feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
--- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt	Thu May 13 19:31:09 2021 +0200
@@ -62,6 +62,11 @@
     const val REQ_ATTR_BASE_HREF = "base_href"
 
     /**
+     * Key for the request attribute containing the RSS feed href.
+     */
+    const val REQ_ATTR_FEED_HREF = "feed_href"
+
+    /**
      * Key for the request attribute containing the full path information (servlet path + path info).
      */
     const val REQ_ATTR_PATH = "requestPath"
--- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Thu May 13 19:31:09 2021 +0200
@@ -86,6 +86,12 @@
             request.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, baseHref + value)
         }
 
+    var feedPath = ""
+        set(value) {
+            field = value
+            request.setAttribute(Constants.REQ_ATTR_FEED_HREF, baseHref + value)
+        }
+
     /**
      * The view object.
      *
@@ -98,9 +104,21 @@
         }
 
     /**
+     * Additional port info, if necessary.
+     */
+    private val portInfo =
+        if ((request.scheme == "http" && request.serverPort == 80)
+            || (request.scheme == "https" && request.serverPort == 443)
+        ) "" else ":${request.serverPort}"
+
+    /**
      * The base path of this application.
      */
-    val baseHref get() = "${request.scheme}://${request.serverName}:${request.serverPort}${request.contextPath}/"
+    val baseHref get() = "${request.scheme}://${request.serverName}$portInfo${request.contextPath}/"
+
+    init {
+        feedPath = "feed/projects.rss"
+    }
 
     private fun String.withExt(ext: String) = if (endsWith(ext)) this else plus(ext)
     private fun jspPath(name: String) = Constants.JSP_PATH_PREFIX.plus(name).withExt(".jsp")
@@ -108,10 +126,15 @@
     fun param(name: String): String? = request.getParameter(name)
     fun paramArray(name: String): Array<String> = request.getParameterValues(name) ?: emptyArray()
 
-    fun forward(jsp: String) {
+    private fun forward(jsp: String) {
         request.getRequestDispatcher(jspPath(jsp)).forward(request, response)
     }
 
+    fun renderFeed(page: String? = null) {
+        page?.let { contentPage = it }
+        forward("feed")
+    }
+
     fun render(page: String? = null) {
         page?.let { contentPage = it }
         forward("site")
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Thu May 13 19:31:09 2021 +0200
@@ -28,24 +28,39 @@
 import de.uapcore.lightpit.AbstractServlet
 import de.uapcore.lightpit.HttpRequest
 import de.uapcore.lightpit.dao.DataAccessObject
-import de.uapcore.lightpit.entities.Issue
 import de.uapcore.lightpit.util.IssueFilter
 import de.uapcore.lightpit.util.IssueSorter
+import de.uapcore.lightpit.util.SpecificFilter
 import de.uapcore.lightpit.viewmodel.IssueFeed
+import de.uapcore.lightpit.viewmodel.ProjectFeed
 import javax.servlet.annotation.WebServlet
 
 @WebServlet(urlPatterns = ["/feed/*"])
 class FeedServlet : AbstractServlet() {
 
     init {
-        get("/issues.rss", this::issues)
+        get("/projects.rss", this::projects)
+        get("/%project/issues.rss", this::issues)
+    }
+
+    private fun projects(http: HttpRequest, dao: DataAccessObject) {
+
+        val projects = dao.listProjects()
+
+        http.view = ProjectFeed(projects)
+        http.renderFeed("project-feed")
     }
 
     private fun issues(http: HttpRequest, dao: DataAccessObject) {
-
-        val issues = dao.listIssues(IssueFilter()).sortedWith(IssueSorter.DEFAULT_ISSUE_SORTER)
+        val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) }
+        if (project == null) {
+            http.response.sendError(404)
+            return
+        }
 
-        http.view = IssueFeed(issues.groupBy(Issue::project))
-        http.forward("issues-feed")
+        val issues = dao.listIssues(IssueFilter(SpecificFilter(project))).sortedWith(IssueSorter.DEFAULT_ISSUE_SORTER)
+
+        http.view = IssueFeed(project, issues)
+        http.renderFeed("issues-feed")
     }
 }
\ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu May 13 19:31:09 2021 +0200
@@ -145,12 +145,15 @@
         }
     }
 
+    private fun feedPath(project: Project) = "feed/${project.node}/issues.rss"
+
     data class PathInfos(
         val projectInfo: ProjectInfo,
         val version: Version?,
         val component: Component?
     ) {
-        val issuesHref by lazyOf("projects/${projectInfo.project.node}/issues/${version?.node ?: "-"}/${component?.node ?: "-"}/")
+        val project = projectInfo.project
+        val issuesHref by lazyOf("projects/${project.node}/issues/${version?.node ?: "-"}/${component?.node ?: "-"}/")
     }
 
     private fun withPathInfo(http: HttpRequest, dao: DataAccessObject): PathInfos? {
@@ -186,13 +189,14 @@
         withPathInfo(http, dao)?.run {
 
             val issues = dao.listIssues(IssueFilter(
-                project = SpecificFilter(projectInfo.project),
+                project = SpecificFilter(project),
                 version = version?.let { SpecificFilter(it) } ?: AllFilter(),
                 component = component?.let { SpecificFilter(it) } ?: AllFilter()
             )).sortedWith(DEFAULT_ISSUE_SORTER)
 
             with(http) {
                 view = ProjectDetails(projectInfo, issues, version, component)
+                feedPath = feedPath(project)
                 navigationMenu = activeProjectNavMenu(
                     dao.listProjects(),
                     projectInfo,
@@ -261,6 +265,7 @@
                 projectInfo,
                 dao.listVersionSummaries(projectInfo.project)
             )
+            feedPath = feedPath(projectInfo.project)
             navigationMenu = activeProjectNavMenu(
                 dao.listProjects(),
                 projectInfo
@@ -290,6 +295,7 @@
 
         with(http) {
             view = VersionEditView(projectInfo, version)
+            feedPath = feedPath(projectInfo.project)
             navigationMenu = activeProjectNavMenu(
                 dao.listProjects(),
                 projectInfo,
@@ -342,6 +348,7 @@
                 projectInfo,
                 dao.listComponentSummaries(projectInfo.project)
             )
+            feedPath = feedPath(projectInfo.project)
             navigationMenu = activeProjectNavMenu(
                 dao.listProjects(),
                 projectInfo
@@ -371,6 +378,7 @@
 
         with(http) {
             view = ComponentEditView(projectInfo, component, dao.listUsers())
+            feedPath = feedPath(projectInfo.project)
             navigationMenu = activeProjectNavMenu(
                 dao.listProjects(),
                 projectInfo,
@@ -426,7 +434,9 @@
             val comments = dao.listComments(issue)
 
             with(http) {
-                view = IssueDetailView(issue, comments, projectInfo.project, version, component)
+                view = IssueDetailView(issue, comments, project, version, component)
+                // TODO: feed path for this particular issue
+                feedPath = feedPath(projectInfo.project)
                 navigationMenu = activeProjectNavMenu(
                     dao.listProjects(),
                     projectInfo,
@@ -443,7 +453,7 @@
         withPathInfo(http, dao)?.run {
             val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) ?: Issue(
                 -1,
-                projectInfo.project,
+                project,
             )
 
             // pre-select component, if available in the path info
@@ -464,10 +474,11 @@
                     projectInfo.versions,
                     projectInfo.components,
                     dao.listUsers(),
-                    projectInfo.project,
+                    project,
                     version,
                     component
                 )
+                feedPath = feedPath(projectInfo.project)
                 navigationMenu = activeProjectNavMenu(
                     dao.listProjects(),
                     projectInfo,
@@ -505,7 +516,7 @@
             // TODO: throw validator exception instead of using defaults
             val issue = Issue(
                 http.param("id")?.toIntOrNull() ?: -1,
-                projectInfo.project
+                project
             ).apply {
                 component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1)
                 category = IssueCategory.valueOf(http.param("category") ?: "")
@@ -522,9 +533,9 @@
                 eta = http.param("eta")?.let { if (it.isBlank()) null else Date.valueOf(it) }
 
                 affectedVersions = http.paramArray("affected")
-                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, projectInfo.project.id) } }
+                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, project.id) } }
                 resolvedVersions = http.paramArray("resolved")
-                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, projectInfo.project.id) } }
+                    .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, project.id) } }
             }
 
             val openId = if (issue.id < 0) {
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Thu May 13 19:31:09 2021 +0200
@@ -28,6 +28,11 @@
 import de.uapcore.lightpit.entities.Issue
 import de.uapcore.lightpit.entities.Project
 
+class ProjectFeed(
+    val projects: List<Project>
+) : View()
+
 class IssueFeed(
-    val issues: Map<Project, List<Issue>>
+    val project: Project,
+    val issues: List<Issue>
 ) : View()
\ No newline at end of file
--- a/src/main/resources/localization/strings.properties	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/resources/localization/strings.properties	Thu May 13 19:31:09 2021 +0200
@@ -54,6 +54,11 @@
 error.message = Server Message
 error.returnLink = Return to
 error.timestamp = Timestamp
+feed.issues.description=Feed about recently updated issues.
+feed.issues.title=LightPIT Issues
+feed.projects.description=Feed about tracked projects.
+feed.projects.source=Issues
+feed.projects.title=LightPIT Projects
 issue.affected-versions=Affected Versions
 issue.assignee=Assignee
 issue.category.Bug=Bug
@@ -126,6 +131,4 @@
 version.status.Released=Released
 version.status.Unreleased=Unreleased
 version.status=Status
-version=Version
-feed.issues.title=LightPIT - Issues
-feed.issues.description=Feed about recently updated issues.
\ No newline at end of file
+version=Version
\ No newline at end of file
--- a/src/main/resources/localization/strings_de.properties	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/resources/localization/strings_de.properties	Thu May 13 19:31:09 2021 +0200
@@ -54,6 +54,11 @@
 error.message = Server Nachricht
 error.returnLink = Kehre zurück zu
 error.timestamp = Zeitstempel
+feed.issues.description=Feed \u00fcber k\u00fcrzlich aktualisierte Vorg\u00e4nge.
+feed.issues.title=LightPIT Vorg\u00e4nge
+feed.projects.description=Feed \u00fcber verwaltete Projekte.
+feed.projects.source=Vorg\u00e4nge
+feed.projects.title=LightPIT Projekte
 issue.affected-versions=Betroffene Versionen
 issue.assignee=Zugewiesen
 issue.category.Bug=Fehler
@@ -127,5 +132,3 @@
 version.status.Unreleased=Unver\u00f6ffentlicht
 version.status=Status 
 version=Version
-feed.issues.title=LightPIT - Vorg\u00e4nge
-feed.issues.description=Feed \u00fcber k\u00fcrzlich aktualisierte Vorg\u00e4nge.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/WEB-INF/jsp/feed.jsp	Thu May 13 19:31:09 2021 +0200
@@ -0,0 +1,36 @@
+<%--
+  ~ Copyright 2021 Mike Becker. All rights reserved.
+  ~
+  ~ Redistribution and use in source and binary forms, with or without
+  ~ modification, are permitted provided that the following conditions are met:
+  ~
+  ~ 1. Redistributions of source code must retain the above copyright
+  ~ notice, this list of conditions and the following disclaimer.
+  ~
+  ~ 2. Redistributions in binary form must reproduce the above copyright
+  ~ notice, this list of conditions and the following disclaimer in the
+  ~ documentation and/or other materials provided with the distribution.
+  ~
+  ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  ~ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  ~ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  ~ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  ~ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  ~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  ~ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  --%>
+<%@page contentType="application/rss+xml;charset=UTF-8" trimDirectiveWhitespaces="true" %>
+<%@page import="de.uapcore.lightpit.Constants" %>
+<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<c:set scope="page" var="contentPage" value="${requestScope[Constants.REQ_ATTR_CONTENT_PAGE]}"/>
+<c:set scope="request" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
+<fmt:setLocale scope="request" value="${pageContext.response.locale}"/>
+<fmt:setBundle scope="request" basename="localization.strings"/>
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0">
+    <c:import url="${contentPage}"/>
+</rss>
--- a/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Thu May 13 19:31:09 2021 +0200
@@ -22,37 +22,24 @@
   ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   --%>
-<%@ page contentType="application/rss+xml;charset=UTF-8" trimDirectiveWhitespaces="true" %>
-<%@page import="de.uapcore.lightpit.Constants" %>
+<%@page contentType="application/rss+xml;charset=UTF-8" pageEncoding="UTF-8" %>
 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
 <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssueFeed" scope="request"/>
-<c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
-<fmt:setLocale scope="request" value="${pageContext.response.locale}"/>
-<fmt:setBundle scope="request" basename="localization.strings"/>
-<?xml version="1.0" encoding="utf-8"?>
-<rss version="2.0">
-    <c:forEach items="${viewmodel.issues}" var="feed">
-        <c:set var="project" value="${feed.key}"/>
-        <c:set var="issues" value="${feed.value}"/>
-        <channel>
-            <title>
-                <fmt:message key="feed.issues.title"/> - <c:out value="${project.name}"/>
-            </title>
-            <link>${baseHref}projects/${project.node}/</link>
-            <description><fmt:message key="feed.issues.description"/></description>
-            <language>${pageContext.response.locale.language}</language>
+<channel>
+    <title><c:out value="${viewmodel.project.name}"/> | <fmt:message key="feed.issues.title"/></title>
+    <description><fmt:message key="feed.issues.description"/></description>
+    <link>${baseHref}projects/${viewmodel.project.node}</link>
+    <language>${pageContext.response.locale.language}</language>
 
-            <c:forEach items="${issues}" var="issue">
-                <item>
-                    <title><c:if test="${not empty issue.component}"><c:out value="${issue.component.name}"/> - </c:if><c:out value="${issue.subject}"/></title>
-                    <description><c:out value="${issue.description}"/></description>
-                    <category><fmt:message key="issue.category.${issue.category}"/></category>
-                    <link>${baseHref}projects/${issue.project.node}/issues/-/${empty issue.component ? '-' : issue.component.node}/${issue.id}</link>
-                    <guid isPermaLink="true">${baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}</guid>
-                    <pubDate><fmt:formatDate value="${issue.updated}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
-                </item>
-            </c:forEach>
-        </channel>
+    <c:forEach items="${viewmodel.issues}" var="issue">
+        <item>
+            <title><c:if test="${not empty issue.component}"><c:out value="${issue.component.name}"/> - </c:if><c:out value="${issue.subject}"/></title>
+            <description><c:out value="${issue.description}"/></description>
+            <category><fmt:message key="issue.category.${issue.category}"/></category>
+            <link>${baseHref}projects/${issue.project.node}/issues/-/${empty issue.component ? '-' : issue.component.node}/${issue.id}</link>
+            <guid isPermaLink="true">${baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}</guid>
+            <pubDate><fmt:formatDate value="${issue.updated}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
+        </item>
     </c:forEach>
-</rss>
\ No newline at end of file
+</channel>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/WEB-INF/jsp/project-feed.jsp	Thu May 13 19:31:09 2021 +0200
@@ -0,0 +1,43 @@
+<%--
+  ~ Copyright 2021 Mike Becker. All rights reserved.
+  ~
+  ~ Redistribution and use in source and binary forms, with or without
+  ~ modification, are permitted provided that the following conditions are met:
+  ~
+  ~ 1. Redistributions of source code must retain the above copyright
+  ~ notice, this list of conditions and the following disclaimer.
+  ~
+  ~ 2. Redistributions in binary form must reproduce the above copyright
+  ~ notice, this list of conditions and the following disclaimer in the
+  ~ documentation and/or other materials provided with the distribution.
+  ~
+  ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  ~ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  ~ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  ~ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  ~ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  ~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  ~ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  --%>
+<%@page contentType="application/rss+xml;charset=UTF-8" pageEncoding="UTF-8" %>
+<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectFeed" scope="request"/>
+<channel>
+    <title><fmt:message key="feed.projects.title"/></title>
+    <description><fmt:message key="feed.projects.description"/></description>
+    <link>${baseHref}projects/</link>
+    <language>${pageContext.response.locale.language}</language>
+
+    <c:forEach items="${viewmodel.projects}" var="project">
+        <item>
+            <title><c:out value="${project.name}"/></title>
+            <description><c:out value="${project.description}"/></description>
+            <link>${baseHref}projects/${project.node}</link>
+            <source url="${baseHref}feed/${project.node}/issues.rss"><fmt:message key="feed.projects.source"/></source>
+        </item>
+    </c:forEach>
+</channel>
--- a/src/main/webapp/WEB-INF/jsp/site.jsp	Thu May 13 18:01:56 2021 +0200
+++ b/src/main/webapp/WEB-INF/jsp/site.jsp	Thu May 13 19:31:09 2021 +0200
@@ -33,6 +33,9 @@
 <%-- Make the base href easily available at request scope --%>
 <c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
 
+<%-- The feed URL for this page. --%>
+<c:set scope="page" var="feedHref" value="${requestScope[Constants.REQ_ATTR_FEED_HREF]}"/>
+
 <%-- Define an alias for the request path --%>
 <c:set scope="page" var="requestPath" value="${requestScope[Constants.REQ_ATTR_PATH]}"/>
 
@@ -62,8 +65,7 @@
         <meta http-equiv="refresh" content="0; URL=${redirectLocation}">
     </c:if>
     <link rel="stylesheet" href="lightpit.css" type="text/css">
-    <link rel="alternate" type="application/rss+xml"
-          title="RSS" href="${baseHref}feed/issues.rss" />
+    <link rel="alternate" type="application/rss+xml" title="RSS" href="${feedHref}" />
     <c:if test="${not empty extraCss}">
         <c:forEach items="${extraCss}" var="cssFile">
         <link rel="stylesheet" href="${cssFile}" type="text/css">

mercurial